线性结构(单/双向链式结构)

        顺序表的查询很快,但是插入和删除性能不行,一方面有元素移动的问题,另一方面也会有扩容缩容的问题。
        那么为解决这个问题,就有了一个新的线性结构- 链表:链表是概念上、逻辑上的连续结构,在物理上链表是非连续、非顺序的。

  1. 链表的最大优势就在于插入和删除的性能非常高。
  2. 链表的问题在于根据地址获取数据,性能高,需要逐步寻址。
  3. 链表不是通过数组实现的,而是通过节点实现的 。              
/**
 *节点类是 MySingleLinkedList<T>类的内部类。
*/
//节点类设计
    public class Node{
        T item;//节点存储的数据
        Node next;//下一个节点的地址
        public Node(T item,Node next){
            this.item=item;
            this.next=next;
        }
    }

         1.单向链表的实现:

        单向链表是指每个节点只有一个数据变量一个地址变量组成,数据变量用于存储当前节点的数据,地址变量用于存储下一个节点的地址。
        单向链表类的设计:

                public class MySingleLinkedList<T> {
                        //成员变量
                                Node head;//存储链表的起始位置
                                int size;//记录链表中的元素数量
                        //构造方法
                                public MySingleLinkedList();

//成员变量
    Node head;//存储链表的起始位置(头部节点不存储数据)
    int size;//记录链表中的元素数量
    //构造方法
    public MySingleLinkedLists(){
        //将头部的节点进行初始化
        this.head=new Node(null,null);
        //将size初始化
        this.size=0;
    }


                        //常用方法
                                public int size();//获取当前存储元素的数量

//常用方法
    //获取当前存储元素的数量
    public int size(){
        return this.size;
    }


                                public T get(int pos);//获取指定位置的元素

//获取指定位置的元素
    public T get(int pos){
        return getNode(pos).item;
    }
    
    //获取指定位置的节点
    public Node getNode(int pos){
        //在指定位置加入元素时,是0位置时
        if (pos==-1){
            return this.head;
        }
        //获取0位置的元素
        Node target=this.head.next;
        //通过遍历找到指定位置的元素。
        for (int i = 0; i < pos; i++) {
            target=target.next;
        }
        return target;
    }


                                public void add(T t);//在尾部添加元素

//在尾部添加元素
    public void add(T t){
        //创建插入的节点
        Node node=new Node(t,null);
        if (this.size==0){
            //插入head的next
            this.head.next=node;
        }else {
            //找到最后一个节点
            this.getNode(size-1).next=node;
        }
        //累计的数量加1
       this.size++;
    }

                                public void add(int pos,T t); //在指定位置添加元素

 //在指定的位置添加元素
    public void add(int pos,T t){
        //获取需要插入的节点
        Node node=new Node(t,null);
        //根据pos获取插入点的节点
        Node current=this.getNode(pos);
        //获取pos前面位置的节点
        Node before=this.getNode(pos-1);
        //将before的next节点指向需要插入的节点
        before.next=node;
        //将插入节点的next指向当前pos上的节点
        node.next=current;
        //累计加1
        this.size++;
    }


                                public T remove(int pos); //删除指定位置的元素

//删除指定位置的元素
    public T remove(int pos){
        //获取pos前面位置的节点
        Node before=this.getNode(pos-1);
        //根据pos获取需要删除的节点
        Node current=this.getNode(pos);
        //将before的next节点指向需要删除的节点的next
        before.next=current.next;
        //累计减1
        this.size--;
        return current.item;
    }

         测试类:

package ListStructure;

public class MyTest {
    public static void main(String[] args) {
        MySingleLinkedLists<String> list=new MySingleLinkedLists<>();
        for (int i = 0; i < 10; i++) {
            //在末尾加入数据
            list.add("我是第"+(i+1)+"个加入数据");
        }

            list.add(0,"我是插入的数据");
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
        System.out.println("我是获取的数据:"+list.get(5));
        System.out.println("我是链表的size:"+list.size());
        String rem=list.remove(2);
        System.out.println("我被删了:"+rem);
    }
}

        2.单向链表的反转:

         方法1:利用一个临时的反转头实现
        1.先创建一个临时的反转头节点(已经反转的链表的起点)
        2.从现有的头节点中取出next节点(0号节点),然后将现有的头节点指向next的next节点(0号节点的下一个节点)
        3.将取出来的0号节点的next指向反转头节点的next节点
        4.并且将反转头的next节点指向0号节点(相当于将正序的链表中0号节点,插入到已经反转的链表的0号节点处)
        如此反复直到正序链表头的next指向空

//单向链表的反转
    public void  reverse(){
        //创建临时的反转头节点。
        Node newHead=new Node(null,null);
        //循环取出0号节点
        while (this.head.next!=null) {
            //获取头节点的下一个节点。
            Node first = this.head.next;
            //将原来的头节点指向下一个节点的下一个。
            this.head.next = this.head.next.next;
            //将取出的节点的next指向反转头的next
            first.next = newHead.next;
            //将反转头的节点指向取出的节点。
            newHead.next = first;
        }
        //将原头节点指向反转头节点的next
        this.head.next=newHead.next;
    }

 

        方法2:利用递归实现
                递归反转就是从原链表的第一个存储节点开始,依次递归调用反转每一个节点,直到把最后一个反转完毕,整个链表就反转完毕了。

 

 //递归实现反转
    public Node reverse(Node curr){
        //判断是否存在下一个节点
        if (curr.next!=null){
            //通过反转下一个节点,获得prev
            Node prev=reverse(curr.next);
            //将之前的节点的next节点设置成当前的节点
            prev.next=curr;
            //返回curr节点
            return curr;
        }else {
            //当前节点没有下一个节点
            //将头部节点的next指向当前节点
            this.head.next=curr;
            return curr;
        }
    }

    public void reverse2(){
        this.reverse(this.head.next);
    }

         3.快慢指针:

                快慢指针即在链表遍历的时候设计两个指针,一个移动的快一些,一个移动慢一些,通过这样的方式提高一些解决问题的效率

                        1.查找中间值:

 //快慢指针得到链表的中间值
    public T getMiditem(){
        //定义快慢指针
        Node fast=this.head.next;
        Node slow=this.head.next;
        //快指针是慢指针速度的两倍,这样快指针到最后时,慢指针刚好在中间。
        while (fast!=null&&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
        }
        //fast走完,slow刚好在中间
        return slow.item;
    }

                        2.输出链表中倒数第K个节点:

//获取倒数第k个节点的元素
    public T getRKItem(int pos){
        //定义快慢指针
        Node fast=this.head.next;
        Node slow=this.head.next;
        //先让指针走pos-1步
        int before=pos-1;
        for (int i = 0; i < before; i++) {
            fast=fast.next;
        }
        //然后快慢指针都走1步每次
        while (fast.next!=null){
            fast=fast.next;
            slow=slow.next;
        }
        return slow.item;
    }

         4.循环链表:

                 1.使用快慢指针解决链表是否是循环链表的问题:

//是否是循环链表
    public boolean hasCircle(){
        //定义快慢指针
        Node fast=this.head.next;
        Node slow=this.head.next;
        //快指针是慢指针速度的两倍
        while (fast!=null&&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
            //当快慢指针可以重合时,就是循环链表
            if (fast!=null&&fast.equals(slow)){
                return true;
            }
        }
        //指针可以遍历到null值表示没有循环
        return false;
    }

                 2.有环链表的入口:

                当快慢指针相遇时,我们可以判断链表中有环,这时重新设定一个新指针指向链表的初始位置,且步长与慢指针一样为1,则慢指针与“新”指针相遇的地方就是环的入口

//找到有环链表的入口。
    public T getCircleEntry(){
        //定义快慢指针
        Node fast=this.head.next;
        Node slow=this.head.next;
        //快指针是慢指针的两倍速
        while (fast!=null&&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
            //快指针与慢指针重合,表示有环
            if (fast!=null&&fast.equals(slow)){
                Node newPoint=this.head.next;
                //移动新指针和慢指针直到两指针重合
                while (!slow.equals(newPoint)){
                    slow=slow.next;
                    newPoint=newPoint.next;
                }
                //环的入口元素
                return newPoint.item;
            }
        }
        return null;
    }

                 测试类:

package ListStructure;

public class MyTest {
    public static void main(String[] args) {
        MySingleLinkedLists<String> list=new MySingleLinkedLists<>();
        list.add("aa");
        list.add("bb");
        list.add("cc");
        list.add("dd");
        list.add("ee");
        list.add("ff");
        list.add("gg");
        for (int i = 0; i < list.size; i++) {
            System.out.println(list.get(i));
        }
        System.out.println("这是环状链表吗?"+list.hasCircle());
        System.out.println("倒数第3个元素是:"+list.getRKItem(3));
        System.out.println("中间值是"+list.getMiditem());
        //在链表中建立一个环
        MySingleLinkedLists.Node node2=list.getNode(2);
        MySingleLinkedLists.Node nodeList=list.getNode(list.size-1);
        nodeList.next=node2;
        System.out.println("这是循环链表吗?"+list.hasCircle());

        String result=list.getCircleEntry();
        System.out.println("循环链表的入口是:"+result);
    }
}

         5.约瑟夫环:

 //约瑟夫环的实现
    //sum是总数
    //num是间隔数
    public void jsfKill(int sum,int num){
        //创建sum个节点
        for (int i = 1; i <=sum; i++) {
            this.add((T) (i+""));
        }
        //找到最后一个链表元素。
        Node last=this.getNode(this.size-1);
        //将最后一个元素的next设置给第一个元素,形成环状。
        last.next=this.head.next;
        //定义一个指针
        Node target=this.head.next;
        //定义一个计数器
        int count=1;
        while (target.next!=target){

            //将前一个元素进行保存
            Node prev=target;
            //指针每次移动一格,计数器就加1
            target=target.next;
            count++;
            if (count==num){
                System.out.println("删除了的节点是:"+target.item);
                //重置计数器
                count=1;
                //指针移动到下一个节点
                target=target.next;
                //删除目标值后,链接节点
                prev.next=target;
            }
        }
        System.out.println("最后剩下的节点是:"+target.item);
    }

                 测试类

package ListStructure;

public class MyTest2 {
    public static void main(String[] args) {
        MySingleLinkedLists<String> lists=new MySingleLinkedLists<>();
        lists.jsfKill(41,3);
    }
}

                6.双向链式结构:

 

 

                                         1.自己做的双向链表

package ListStructure;

public class MyDoubleLinkedList<T> {
    //节点类
    public class Node{
        //存储数据的变量
        T item;
        //指向上一个节点的引用
        Node prev;
        //指向下一个节点的引用
        Node next;
        public Node (Node prev,T item,Node next){
            this.item=item;
            this.next=next;
            this.prev=prev;
        }
    }

    //声明开始节点
    Node first;
    //声明最后的节点
    Node last;
    //定义链表内容的长度
    int size;

    //构造方法
    public MyDoubleLinkedList(){
        this.size=0;
    }

    //获取当前存储元素的数量
    public int size(){
        return this.size;
    }

    //在尾部添加元素
    public void add(T t){
        //将last存储在prev节点
        Node prev=last;
        //创建需要插入的节点
        Node node=new Node(prev,t,null);
        //加入的元素为第一个元素时
        if (prev==null){
            //将first指针指向当前元素
            first=node;
        }else {
            //将prev节点的next指向新插入的节点
            prev.next=node;
        }

        //将last指向新插入的节点
        last=node;
        //数量增加
        this.size++;
    }

    //根据位置获取节点的方法
    public Node getNode(int pos){
        if (first==null){
            return null;
        }

        //定义中间的变量
        int middle=this.size/2;
        if (pos<middle){
            //从前面开始遍历
            Node x=first;
            for (int i = 0; i <pos; i++) {
                x=x.next;
            }
            return x;
        }else{
            Node x=last;
            //从后面开始遍历
            for (int i = size-1; i > pos; i--) {
                x=x.prev;
            }
            return x;
        }
    }

    //在指定位置插入节点
    public void add(int pos,T t){
        if (pos==this.size){
           //直接在尾部添加
            add(t);
        }else {
            //创建新节点
            Node node=new Node(null,t,null);
            //获取目标的位置
            Node target=getNode(pos);
            //获取位置的节点就为空
            if (target==null){
                //没有元素时
                first=node;
            }else {
                //获取目标前一个的位置
                Node prev=target.prev;
                //指定新节点的prev到目标的前一个
                node.prev=prev;
                //新节点的next到目标。
                node.next=target;
                //目标节点的prev到新节点
                target.prev=node;
                if (prev==null){
                    //first指向插入的元素
                    first=node;
                }else {
                    //指定目标的前一个的next到新节点
                    prev.next=node;
                }
            }
            this.size++;
        }
        }

        //指定位置的删除
    public void remove(int pos){
            Node target=this.getNode(pos);
            Node prev=target.prev;
            Node next=target.next;
            if (prev==null){
                first=next;
            }else {
                prev.next=next;
            }
           if (next==null){
               last=prev;
           }else {
               next.prev=prev;
           }
            this.size--;
    }
}

                          2.测试类

package ListStructure;

public class Test12 {
    public static void main(String[] args) {
        MyDoubleLinkedList<String> list=new MyDoubleLinkedList<>();
        list.add("aa");
        list.add("bb");
        list.add("cc");
        list.add("dd");
        list.add("ee");
        list.add("ff");
        list.add(0,"在第一个位置插入的元素");
        list.add(4,"我是随便");
        list.add(list.size(),"我是最后一个元素的插入");
        list.remove(list.size()-1);
        for (int i = 0; i < list.size(); i++) {
            System.out.println("获得的元素是"+list.getNode(i).item);
        }
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值