(基于Java)数据结构与算法分析----单链表+双向链表

单链表

概述

百度百科:单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。
简单来说,链表就是,把一个一个的数据集给串起来,然后形成的串串。数据集在链表中就称为节点

Java中的链表和C语言的比较类似,不过这里我们不需要使用指针

节点:
链表是由节点组成的,一个节点存储的数据分为两部分,第一部分是我们需要存储的数据,另一部分是下一个节点的地址(即引用)。C语言中节点是结构体,在Java中,创建节点类,用对象替代节点。每个节点必须含有一个属性用来指向下一个节点,此属性类型设置为节点类,此属性作用等同于C语言结构体中的next指针,即存储下一个节点对象的地址。
在这里插入图片描述no是编号,name是姓名
头结点:
单链中有一个很重要的节点,即头头节点,单链表,头节点就是一切,没了头节点整个单链表就无法遍历了。头结点可以不存放数据,仅存储下一个节点的地址,也可用存放数据。
我们这里使用头结点仅存储下一个节点的地址。

增删改+打印

插入节点(创建)

这里,创建头结点,同时创建一个节点对象,把head节点的地址也赋值为新建的节点,防止直接在head上面动刀
在这里插入图片描述
在这里插入图片描述
这里为了测试的时候记忆操作,把方法的形参设置为一个数组,到时候传入存放节点的数组,再由此对数组遍历,然后插入节点。

修改

首先接入新节点(形参),此处也是仅记录一个简单的修改的思路,找到节点,然后修改
在这里插入图片描述

删除

此处仅删除一个固定的节点,仅记录一下删除的思路,就是当某个节点不被别人引用,则等于她就被删除了,然后让被删除的节点的上一个节点指向被删除节点的下一个节点
在这里插入图片描述

打印链表

简单
在这里插入图片描述

几个面试题

单链表在面试中也是热点话题

求链表中有效节点个数

这个简单,直接遍历即可
在这里插入图片描述

查找倒数第K个节点

查找倒数第K个节点,因为单链表的特殊,我们不可能从链表尾遍历,所以需要先得出链表有几个有效节点,然后用有效节点数减去K,即得到从第一个有效节点开始到倒数第K个节点间需要遍历的次数
在这里插入图片描述

单链表的反转

所谓单链表的反转,就是把原来的链表反过来,即比如12345变为54321
此处思路是根据原链表来进行头插法,从而得到一个新链表,再把原链表的头指向新链表的第一个有效节点,按照单链表的特性(找到头结点,就等于找到了整个链表),此时便实现了链表的反转。
具体的实现方法:遍历原链表,每遍历一个节点,就把它从这个链表中"取出",即让他的next域指向新链表的第一个有效节点(当新链表只有头结点,则正好指向NULL,表示此节点会作为新链表的链表尾),然后使新链表的头的next域再指向此节点,这样便实现了一个节点的删除和一个节点的插入,此处的插入便是头插法
在这里插入图片描述

单链表的反向打印

单链表的反向打印也可以先把链表反转,再打印,再反转回去,但是不仅扯淡,还效率低。
我们可以使用栈,栈也是一种数据容器,类似于集合,他的特点是先进后出
可以使用Stack<T> 名=new Stack<T>();来创建一个栈
栈的存放数据可以使用他的push()add()方法
取数据可以使用pop()方法,不能指定取的数据,会自动根据进入的先后顺序来取出
size()方法可以返回当前栈中的元素数
在这里插入图片描述

两链表合并,并保持顺序一致性

思路,可以遍历其中一个链表,每遍历一个节点,便插入到另一个链表中,在插入的过程中同时判断其序号大小来进行排序

因为此链表为了测试方便,把add方法的参数写成了数组,所以此处需要把一个链表中的有效节点全部存入数组,再把数组传给add方法来进行插入
在这里插入图片描述

测试

测试部分代码,结果就不截图了,麻烦

public static void main(String[] args)   {
        Node node1=new Node(1,"松江");
        Node node2=new Node(3,"吴用");
        Node node3=new Node(2,"卢俊义");
        Node node4=new Node(3,"吴用");
        Node node5=new Node(4,"牛蛙一号");
        Node node6=new Node(5,"牛蛙二号");
        Node node7=new Node(6,"牛蛙三号");
        Node node8=new Node(7,"牛蛙四号");
        Node node9=new Node(8,"牛蛙五号");
        Node node10=new Node(10,"牛蛙五号");
        Node node[]={node6,node7,node8,node9};
        Node node0[]={node1,node2,node3,node4,node5,node10};
        SingleLinkedList linkedList=new SingleLinkedList();
        SingleLinkedList linkedList1=new SingleLinkedList();
        linkedList.add(node);
        linkedList1.add(node0);
        Scanner sc=new Scanner(System.in);
        String n="1";
        while(true){
            System.out.print("0,退出\n"+"1,修改\n"+"2,显示所有\n"+"3,删除\n"+"4,得到倒数第K个节点\n"+"5,链表反转\n"+"6,链表反向打印\n"+"7,两链表合并\n");
            n = sc.next();
            if(n.equals("0")){
                break;
            }else if (n.equals("1")){
                linkedList.update(new Node(6,"大头"));
                linkedList.list();
            }else if(n.equals("2")){
                linkedList.list();
            }else if(n.equals("3")){
                linkedList.delect(3);
                linkedList.list();
            }else if(n.equals("4")){
                int K=0;
                String K1=null;
                while (true){
                    System.out.println("请输入K值");
                    K1=sc.next();
                    try {
                        int k= Integer.parseInt(K1);
                        break;
                    }catch (Exception e){
                        System.out.println("输入错误");
                        continue;
                    }
                }

                try {
                    Node node000=linkedList.getLastNode(K);
                    System.out.println(node000);
                }catch (Exception e){
                    System.out.println(e.getMessage());
                }
            }else if(n.equals("5")){
                linkedList.reversal();
                linkedList.list();
            }else if(n.equals("6")){
                linkedList.reversalShow();
            }else if(n.equals("7")){
                linkedList.merge(linkedList1);
                linkedList.list();
            }else {
                System.out.println("输入错误");
            }
        }
        System.out.println("退出成功");
    }

所有代码

如下:

import java.util.Scanner;
import java.util.Stack;

public class SingleLinkedListDemo {
    public static void main(String[] args)   {
        Node node1=new Node(1,"松江");
        Node node2=new Node(3,"吴用");
        Node node3=new Node(2,"卢俊义");
        Node node4=new Node(3,"吴用");
        Node node5=new Node(4,"牛蛙一号");
        Node node6=new Node(5,"牛蛙二号");
        Node node7=new Node(6,"牛蛙三号");
        Node node8=new Node(7,"牛蛙四号");
        Node node9=new Node(8,"牛蛙五号");
        Node node10=new Node(10,"牛蛙五号");
        Node node[]={node6,node7,node8,node9};
        Node node0[]={node1,node2,node3,node4,node5,node10};
        SingleLinkedList linkedList=new SingleLinkedList();
        SingleLinkedList linkedList1=new SingleLinkedList();
        linkedList.add(node);
        linkedList1.add(node0);
        Scanner sc=new Scanner(System.in);
        String n="1";
        while(true){
            System.out.print("0,退出\n"+"1,修改\n"+"2,显示所有\n"+"3,删除\n"+"4,得到倒数第K个节点\n"+"5,链表反转\n"+"6,链表反向打印\n"+"7,两链表合并\n");
            n = sc.next();
            if(n.equals("0")){
                break;
            }else if (n.equals("1")){
                linkedList.update(new Node(6,"大头"));
                linkedList.list();
            }else if(n.equals("2")){
                linkedList.list();
            }else if(n.equals("3")){
                linkedList.delect(3);
                linkedList.list();
            }else if(n.equals("4")){
                int K=0;
                String K1=null;
                while (true){
                    System.out.println("请输入K值");
                    K1=sc.next();
                    try {
                        int k= Integer.parseInt(K1);
                        break;
                    }catch (Exception e){
                        System.out.println("输入错误");
                        continue;
                    }
                }

                try {
                    Node node000=linkedList.getLastNode(K);
                    System.out.println(node000);
                }catch (Exception e){
                    System.out.println(e.getMessage());
                }
            }else if(n.equals("5")){
                linkedList.reversal();
                linkedList.list();
            }else if(n.equals("6")){
                linkedList.reversalShow();
            }else if(n.equals("7")){
                linkedList.merge(linkedList1);
                linkedList.list();
            }else {
                System.out.println("输入错误");
            }
        }
        System.out.println("退出成功");
    }
}

//管理链表的类
class SingleLinkedList{
//    创建头结点,头结点仅作为一个标记,不存放数据
    private Node head=new Node(0,"");
    private Node temp;    //防止head头结点发生变动,此处创建一个节点用来代替头结点
//    增加节点的方法
    public void add(Node[] nodeArray){
        temp=head;

//        将遍历链表的操作和插入节点的操作分开

//        设置一个标记,用于判断编号是否重复
        boolean flag;
//        遍历,按照编号从小到大,找到新增节点应该插入的位置
        for (int i = 0; i < nodeArray.length; i++) {
//            设置初始值为flase,当编号重复,则设置flag变为true
            flag=false;
            while(true){
                if (temp.next==null){
                    break;
                }
                if (temp.next.no>nodeArray[i].no){
                    break;
                }
                if (temp.next.no==nodeArray[i].no){
                    flag=true;
                    break;
                }
                temp=temp.next;
            }
//            插入操作
            if (flag){
                System.out.println(nodeArray[i].no+"编号重复");
                continue;
            }else {
                nodeArray[i].next=temp.next;
                temp.next=nodeArray[i];
            }
        }
    }

//    删除
    public void delect(int no){
        if (head.next==null){
            System.out.println("链表为空");
            return;
        }
//        遍历 查找
        temp=head;
        while (true){
            if (temp.next==null){
                System.out.println("未找到");
                return;
            }
            //    删除需要用temp.next去查找,不能用temp。
            //    如果用temp当找到要被删除的数据,则五法找到其前一个节点,然后就没法删除
            if (temp.next.no==no){
                break;
            }
            temp=temp.next;
        }
//        找到要删除的节点后,使此节点的前一个节点和此节点的后一个节点相连接,那么此节点不被引用,则会被垃圾回收
        temp.next=temp.next.next;
    }

//    显示链表所有的数据
    public void list(){
        if (head.next==null){
            System.out.println("链表空");
        }else {
            temp=head.next;   //头结点无数据,所以直接让temp指向第二个节点
            while (temp!=null){
                System.out.println(temp);
                temp=temp.next;
            }
        }

    }

//    修改节点
    public void update(Node node){
//        首先判断链表是否为空
        if (head.next==null){
            System.out.println("链表为空");
            return;
        }
        temp=head.next;

//        这里遍历查找对应编号的节点,遍历至使temp指向要修改的节点
        while (true){
            if (temp==null){
//                遍历到链表尾那就说明未找到,直接return
                System.out.println("未找到");
                return;
            }
            if (temp.no==node.no){
                break;
            }
            temp=temp.next;
        }

//        找到后对链表数据进行修改
        temp.name=node.name;
    }

//    得到有效节点个数
    public int getLong(){
        temp=head;
        int n=0;
        while(temp.next!=null){
            temp=temp.next;
            n++;
        }
        return n;
    }

//    得到倒数第K个节点
    public Node getLastNode(int n){
        int Long=getLong();
        if (Long==0){
            throw new RuntimeException("链表为空");
        }
        temp=head.next;
        for (int i = 0; i < Long-n; i++) {
            temp=temp.next;
        }
        return temp;
    }

//    链表反转
    public void reversal(){
        if (head.next==null||head.next.next==null){
            System.out.println("数据不足,无需反转");
            return;
        }
        Node head1=new Node(0,"");
        temp=head.next;
        Node next =null;
        while (temp!=null) {
            next = temp.next;
            temp.next=head1.next;
            head1.next=temp;
            temp=next;
        }
        head.next=head1.next;
    }

//    链表反向打印
    public void reversalShow(){
        if (head.next==null){
            System.out.println("链表空");
            return;
        }
        temp=head.next;
        Stack<Node> stack=new Stack<Node>();
        while (temp!=null){
            stack.push(temp);
            temp=temp.next;
        }
        while (stack.size()>0){
            System.out.println(stack.pop());
        }
    }
//    得到头结点
    public Node getHead(){
        return head;
    }
//    把另一个链表并入此链表
    public void merge(SingleLinkedList linkedList1){
        int n=linkedList1.getLong();
        temp=linkedList1.getHead().next;
        if (n==0){
            System.out.println("第二联表为空");
            return;
        }
        Node node[]=new Node[n];
        for (int i = 0; i < n; i++) {
            node[i]=temp;
            temp=temp.next;
        }
        add(node);
    }
}

//创建节点类
class Node{
    public int no;
    public String name;
    public Node next=null;

    public Node(int no, String name) {
        this.no = no;
        this.name = name;
    }

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

双向链表

概述

双向链表和单链表都是线性结构,不同的是双向链表他是双向的,除头和尾节点,其他每个节点不仅可以指向下一个节点,也指向此节点的上一个节点。即可以直接根据一个节点找到他的上一个节点。这样的操作让链表的删除操作更加简单

双向链表的实现

节点的设计

比单链表的节点多一个节点类型的pre属性,用于指向节点的上一个节点
在这里插入图片描述

节点的插入

按照从小到大插入
在这里插入图片描述

删除

删除操作要比单链表的删除操作简单
在这里插入图片描述

全部代码

import java.util.Scanner;

public class DoubleLinkedListDemo {
    public static void main(String[] args) {
        NodeDouble node1=new NodeDouble(1,"松江");
        NodeDouble node2=new NodeDouble(3,"吴用");
        NodeDouble node3=new NodeDouble(2,"卢俊义");
        NodeDouble node4=new NodeDouble(3,"吴用");
        NodeDouble node5=new NodeDouble(4,"牛蛙一号");
        NodeDouble node6=new NodeDouble(5,"牛蛙二号");
        NodeDouble node7=new NodeDouble(6,"牛蛙三号");
        NodeDouble node8=new NodeDouble(7,"牛蛙四号");
        NodeDouble node9=new NodeDouble(8,"牛蛙五号");
        NodeDouble node10=new NodeDouble(10,"牛蛙五号");
        NodeDouble node0[]={node1,node2,node3,node4,node5,node10,node6,node7,node8,node9};
        DoubleLinkedList linkedList=new DoubleLinkedList();
        linkedList.add(node0);

        Scanner sc=new Scanner(System.in);
        String n;
        while(true) {
            System.out.print("0,退出\n" + "1,显示所有\n" + "2,删除\n");
            n = sc.next();
            if (n.equals("0")) {
                break;
            }else if(n.equals("1")){
                linkedList.list();
            }else if (n.equals("2")){
                int K=0;
                String K1;
                while (true){
                    System.out.println("请输入K值");
                    K1=sc.next();
//                    为保证程序的健壮性,这里防止用户输入的不是数字,此处做一个整形转换,用try catch包裹一下,转换失败则让用户重新输入
                    try {
                        int k= Integer.parseInt(K1);
                        linkedList.delect(k);
                        break;
                    }catch (Exception e){
                        System.out.println("输入错误");
                        continue;
                    }
                }
            }
        }
        System.out.println("退出成功");
    }
}

//管理链表的类
class DoubleLinkedList{
    private NodeDouble head=new NodeDouble(0,"");  //创建头结点,头结点不存放数据
    private NodeDouble temp=null;  //temp用于保存head

//    插入节点
    public void add(NodeDouble node1[]){  //这里设置的行参是数组是为了方便一次出入多个节点
        NodeDouble node=null;  //先创建一个node类型的变量,用于后面从数组里取数据
        temp = head;  //初始化temp
        boolean flag;  //定义一个标记,在后面用于判断节点编号是否重复
        for (int i = 0; i <node1.length ; i++) {  //这里的for循环不重要,用于遍历得到数组中的数据
            flag=false;   //每次循环进来把flag默认设置为flash
            node=node1[i];      //把数组中的数据放在变量中
            while (temp.next != null) {  //while循环体用于比较节点的编号,根据链表从小到大的顺序来插入数据
//                此处对链表进行遍历下面是依次比较链表中节点编号的大小,当找到的节点编号比传入节点编号大的时候,则跳出循环
                if (node.no < temp.next.no) {
                    break;
                } else if (node.no == temp.next.no) {  //当编号重复,则令flag变为true
                    flag=true;
                    break;
                }
                temp = temp.next;
            }
            if(flag) {  //当flag为true,表示编号重复,则跳过这次的for循环
                System.out.println(node.no+"编号重复,此节点自动忽略");
                continue;
            } else if (temp.next == null) {  //当temp.next==null,则表示遍历到链表尾,直接节点添加到链表尾
//                下面两行用于将节点连接到链表尾
                temp.next = node;
                node.pre = temp;
            } else {
//                节点插入位置不是尾部则需要四次操作
                node.next = temp.next;
                temp.next.pre = node;
                temp.next = node;
                node.pre = temp;
            }
        }
    }

    //    显示链表所有的数据,和单链表一模一样
    public void list(){
        if (head.next==null){
            System.out.println("链表空");
        }else {
            temp=head.next;   //头结点无数据,所以直接让temp指向第二个节点
            while (temp!=null){
                System.out.println(temp);
                temp=temp.next;
            }
        }
    }

    //    删除。删除操作比单链表节点,不需要刻意的留存待删除节点的上一个节点
    public void delect(int no){
        if (head.next==null){  //正常的先判断链表是否为空
            System.out.println("链表为空");
            return;
        }
//        遍历 查找
        temp=head.next;  //直接初始化temp为链表第一个有效节点
        while (true){   //while循环用于遍历链表查找要删除的节点
            if (temp==null){  //当temp==null,则表示遍历结束了,此时若还未找到,则表示链表中没有对应编号的数据
                System.out.println("未找到");
                return;
            }
            if (temp.no==no){  //找到直接退出循环
                break;
            }
            temp=temp.next;  //更新temp值
        }
//        找到要删除的节点后,使此节点的前一个节点和此节点的后一个节点相连接,那么此节点不被引用,则会被垃圾回收
        temp.pre.next=temp.next;
    }
}

//创建节点类
class NodeDouble{
    public int no;
    public String name;
    public NodeDouble next=null;  //next用于指向节点的下一个节点
    public NodeDouble pre=null;  //pre用于指向节点的上一个节点

    public NodeDouble(int no, String name) {
        this.no = no;
        this.name = name;
    }

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

注意事项

这里需要多多体会temp用head或head.next的区别差异,同时!精华都在注释中!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值