数据结构笔记(单链表)

文章目录

前言

一、单链表的定义

二、单链表的结构

三、单链表的实现

1.结点类的定义

2.定义单链表泛型类

3.单链表中的基本算法

1)以头插法建立表单

2)以尾插法建立表单

3)获取链表长度

4)将元素插入链表末尾

4)获取第i位的值

5)替换指定位的元素

6)获取第一个值为data的元素的序号

7)删除第i位的元素

8)将新的结点插入在特定结点后面

四、单链表的应用

1.合并链表问题

2.快慢指针法

总结


 

前言

链表结构是线性表的一种结构。链表中的每一个结点不仅包括了其本身的信息,还设有结点包含了元素间的逻辑关系,即包含有后继结点或前驱结点的地址信息,这些含有逻辑关系结点称为指针成员。链表的优点在于便于修改。而链表分为单链表和双链表。单链表表示每一个结点只设有一个指针成员指向其后继结点,因此单链表只能单向遍历。

 

一、单链表的定义

每一个结点只设置一个指向后继结点的指针成员的表单称为单链表。在单链表中,只能按顺序访问结点的后继结点。

 

二、单链表的结构

单链表包含一个头节点head,用.next的方式连接后继结点,末尾元素的后继结点需设为null。如第一项数据为head.next。

 

三、单链表的实现

1.结点类的定义

public class LinkedNode {    //将表示结点的类设置为泛型类
    E data;                     //表示该结点中包含的数据
    LinkedNode next;         //指针成员,单链表中只含有一个指向后继结点的指针成员
    public LinkedNode(){        //无参构造方法
        next = null;
    }
    public LinkedNode(E data){  //在创建结点时传入该结点的数据
        this.data = data;
        next = null;
    }
}

2.定义单链表泛型类

public class LinkedListClass {    //将该类设为泛型类
    LinkedNode head;              //头结点对象
    public LinkedListClass(){        //构造方法
        head = new LinkedNode();  //实现头结点
        head.next = null;            //头节点的后继结点设为null
    }
}

该构造方法使得我们在创建单链表对象时,得到的是一个只包含了头节点的空链表。

3.单链表中的基本算法

1)以头插法建立表单

单链表的整体创建分为头插法和尾插法,头插法是将新结点dataNode插入到表头后,即head.next=dataNode。用该方法得到的链表跟原数组结构的顺序相反。

数据结构笔记(单链表)_第1张图片

同理,将后续的元素逐个插入

   数据结构笔记(单链表)_第2张图片

代码如下

    /**
     * 以头插法将数组转换为链表结构
     * @param arr: 数据数组
     */
    public void createFromFirst(E[] arr){
        LinkedNode dataNode;                     //创建变量表示要插入的元素
        for (int i = 0; i < arr.length; i++) {      //遍历数组获取每一位的元素
            dataNode = new LinkedNode(arr[i]);   //为该变量赋值,值为数组中的值
            dataNode.next = head.next;              //如步骤2
            head.next = dataNode;                   //如步骤3
        }
    }

 

2)以尾插法建立表单

与头插法不同,尾插法是将结点插入在尾结点之前,得到的链表与原数组顺序相同。

数据结构笔记(单链表)_第3张图片

在遍历插入数组元素后,指针依旧是指向尾元素,此时需将该元素的后继结点设为null。

    /**
     * 以尾插法建立链表结构
     * @param arr: 数组数据
     */
    public void createFromLast(E[] arr){
        LinkedNode dataNode;                    //创建变量表示要插入的元素
        LinkedNode pointer = head;              //表示指针,指向尾元素(此时该链表为空链表,头节点即尾结点)
        for (int i = 0; i < arr.length; i++) {     //遍历数组获取每一位的元素
            dataNode = new LinkedNode(arr[i]);  //为该变量赋值,值为数组中的值
            pointer.next = dataNode;               //如步骤2
            pointer = dataNode;                    //如步骤3
        }
        pointer.next = null;                       //将尾结点的后继结点设为null
    }

3)获取链表长度

    /**
     * 获取链表长度
     * @return: 链表长度
     */
    public int getSize(){
        LinkedNode pointer = head;  //初始化指针指向头节点
        int size = 0;                  //定义变量size,初始值为0
        while (pointer.next!=null){    //循环遍历每一项,当pointer的后继结点为null时,链表读到末尾
            pointer = pointer.next;    //将指针后移一位
            size++;                    //size自增1
        }
        return size;                   //返回size的值
    }

4)将元素插入链表末尾

将元素插入末尾时,需先遍历链表将指针指到尾结点

    /**
     * 将元素加入链表末尾
     * @param e:需加入的元素
     */
    public void add(E e){
        LinkedNode pointer = head;                    //初始化指针指向头节点
        LinkedNode dataNode = new LinkedNode(e);   //新建结点dataNode
        while (pointer.next!=null){                      //循环遍历每一项,当pointer的后继结点为null时,链表读到末尾
            pointer=pointer.next;                        //将指针后移一位
        }                                                //遍历结束,指针指向尾结点
        pointer.next = dataNode;                         //将dataNode插入尾结点后面
    }

需要注意的是,此时无需将dataNode的后继结点设为null,因为结点在创建时的后继结点为null

数据结构笔记(单链表)_第4张图片

4)获取第i位的值

    /**
     * 查找序号为i的元素
     * @param i:需查找的序号为i的元素
     * @return :指针所指向的值,即第i位的值
     */
    public LinkedNode getI(int i){
        LinkedNode pointer = head;  //初始化指针指向头节点
        for (int j = 0; j < i; j++) {  //遍历链表直到第i位
            pointer = pointer.next;    //将指针后移一位
        }
        return pointer;                //返回指针所指向的值,即第i位的值
    }

5)替换指定位的元素

    /**
     * 将指定位的元素替换
     * @param i: 第i位
     * @param data: 替换的数据
     */
    public void setEle(int i,E data){
        if(i<0 || i>getSize()){                                    //当i值小于0或大于链表长度时,抛出错误
            throw new IllegalArgumentException("i不在有效范围内");
        }
        getI(i).data = data;                                       //获取第i位的元素并将它的值换为传入的data值
    }

6)获取第一个值为data的元素的序号

    /**
     * 获取第一个值为e的元素序号,当未找到元素时返回-1
     * @param data:要找到的值
     * @return :返回该元素的序号,未找到时返回-1
     */
    public int getNum(E data){
        LinkedNode pointer = head;                      //初始化指针指向头节点
        int num = 0;                                       //定义变量num表示序号,初始化为0
        while (pointer.next!=null && pointer.data!=data){  //遍历链表,当pointer.data等于需找到的值,或者遍历到末尾时,结束循环
            num++;                           
            pointer = pointer.next;                        //指针向后移一位
        }
        if(pointer==null){                                 //当pointer指到末尾,表示未找到元素,此时返回-1
            return -1;
        }
        return num; 
    }

7)删除第i位的元素

在单链表中,删除元素时需获取该元素的前一位元素(如元素a0),再通过a0.next=a0.next.next的方式删除元素。

数据结构笔记(单链表)_第5张图片

代码如下

    /**
     * 删除第i位元素
     * @param i
     */
    public void delete(int i){
        if(i<0 || i>getSize()){
            throw new IllegalArgumentException("i不在有效范围内");
        }
        getI(i-1).next = getI(i-1).next.next;
    }

8)将新的结点插入在特定结点后面

与头插法所示图片相同,先将插入结点与被插入结点的后继结点相连,再将被插入结点与插入结点相连即可

    /**
     *
     * 将data数据插入在position的后面
     * @param data:需要插入的数据
     * @param position:插入结点
     */
    public void insert(E data,E position){
            LinkedNode dataNode = new LinkedNode(data);
            LinkedNode pointer = head;
            while (pointer.next!=null){
                pointer.next = pointer;
                if (pointer.data==data){
                    dataNode.next=pointer.next;    //将dataNode与pointer的后继结点相连
                    pointer.next=dataNode;         //将dataNode作为pointer的后继结点
                    return;
                }
            }
            System.out.println("未找到"+position+"数据");
    }

需要注意的是,插入结点的两行代码顺序不能改变

四、单链表的应用

1.合并链表问题

存在两个整数单链表A和B,设计一个算法将A,B中的所有数据结点以(a0,b0,a1,b1,...) 的方式合并得到单链表C。这种问题的算法称为二路归并算法,其代码如下:

    /**
     * 将两个单链表以(a0,b0,a1,a1,...)的形式合并
     * @param A
     * @param B
     * @return
     */
    public static LinkedListClass combine(LinkedListClass A,LinkedListClass B){
        LinkedListClass C = new LinkedListClass<>();
        LinkedNode aPointer = A.head.next;
        LinkedNode bPointer = B.head.next;
        LinkedNode cPointer = C.head;
        while (aPointer.next!=null && bPointer!=null){  //当A,B链表都未遍历完时,需轮流插入到链表B,故循环条件为A,B都存在元素
            cPointer.next = aPointer;   //由于有顺序要求(a0,b0,a1,b1),故采用尾插法是链表顺序与数组顺序一致
            cPointer = aPointer;
            aPointer = aPointer.next;

            cPointer.next = bPointer;
            cPointer = bPointer;
            bPointer = bPointer.next;
        }
        cPointer.next = null;           //将尾结点的后继结点设为null
        if(aPointer!=null){
            cPointer.next = aPointer;   //将未遍历玩的链表整体接入链表C
        }
        if(bPointer!=null){
            cPointer.next = aPointer;
        }
        return C;
    }

2.快慢指针法

有一个长度大于2的整数单链表,设计一个算法查找该链表的中位数,如(1,2,3,4)返回2。代码如下

    /**
     * 获取Integer类型链表的中间数
     * @param linkedList:
     * @return: 中间数
     */
    public static int middle(LinkedListClass linkedList){
        LinkedNode fastPointer = linkedList.head.next;          //快指针
        LinkedNode slowPointer = linkedList.head.next;          //慢指针
        while (fastPointer.next!=null && fastPointer.next.next!=null){   //快指针无论如何走的都比慢指针快,所以仅需要判断快指针即可,又由于快指针一次走两个结点,所以需要判断两个结点后是否为null
            slowPointer = slowPointer.next;                              //慢指针走一个结点
            fastPointer = fastPointer.next.next;                         //快指针走两个结点
        }
        return slowPointer.data;
    }

 


总结

单链表的难点在于指针的位置,我建议在写代码的时候用纸笔画个图,想清楚指针要怎么移动。

你可能感兴趣的:(笔记,数据结构,链表)