数据结构 - 单链表

数据结构 - 单链表

1 使用Java实现单链表

  • 自定义Node节点类,该类包含:

    • 节点内存储的数据内容Data
    • 该节点指向的下一个节点指针Next
    • 无参(用来构建头节点)和有参的构造方法(用来给链表传输数据)
  • 自定义一个SingleLinkedList类,该类包含:

    • 单向链表头节点HeadNode,该头节点在SingleLinkedList类调用无参方法时进行初始化(节点内Data为null)
    • 链表的长度Length
    • HeadNode的get方法(用于后面测试类中可以获取到头节点)
    • Length的get和set方法(用于后面测试类中获取链表的长度)
    • 三种插入方法(addFirst头插法,addLast尾插法,addByIndex指定位置插入)
    • 按位置查找FindByIndex
    • 按元素值查找FindByElement
    • 按指定位置删除DeleteByIndex
    • 打印链表所有数据ListPrint
    • 链表长度listSize
public class SinglyLinkedList<E> {

     static class Node<E> {
        E Data;
        Node<E> Next;

        public Node() {
            this.Data = null;
        }

        public Node(E data) {
            Data = data;
        }
    }

    private Node HeadNode;
    private int Length = 0;

    public Node getHeadNode() {
        return HeadNode;
    }


    public int getLength() {
        return Length;
    }

    public void setLength(int length) {
        Length = length;
    }

    public SinglyLinkedList() {
        this.HeadNode = new Node<E>();
    }

    /*
        插入操作,分为三种情况:
        1. 头插法 - addFirst - 时间复杂度O(1)
        2. 尾插法 - addLast - 时间复杂度O(n)
        3. 指定位置插入 - addByIndex - 平均时间复杂度O(n/2) - O(n)
     */

    //头插法
    public void addFirst(E e){
    	//创建一个Node节点并把数据放入Data
        Node<E> data = new Node(e);
        //插入节点的Next设置为Head节点的Next
        data.Next = this.HeadNode.Next;
        //Head节点的Next设置为插入节点
        this.HeadNode.Next = data;
        //更新链表长度
        this.Length++;
    }

    //尾插法
    public void addLast(E e) {
        //创建一个Node节点并把数据放入Data
        Node<E> data = new Node(e);
        //判断该链表长度是否为0,如果为0,则直接在头节点后插入该节点
        if (this.Length == 0){
            data.Next = this.HeadNode.Next;
            this.HeadNode.Next = data;
        }
        //如果不是头节点,则需要先找到链表最后一个节点再插入
        else {
            //创建一个temp节点用来当作移动指针,因为链表长度不为0,temp节点初始位置为头节点后第一个节点
            Node<E> tem = this.HeadNode.Next;
            //当移动指针的下一个节点不为空时,指针向后走一个
            //直到移动指针的下一个节点为空,此时temp为最后一个节点
            while (tem.Next != null){
                tem = tem.Next;
            }
			//插入Node的下一个节点设置为空,因为该节点为链表新尾节点
            data.Next = null;
            //设置移动指针(当前链表的尾节点)的下一个节点为插入节点
            tem.Next = data;
        }
        //插入成功后,更新链表长度
        this.Length++;
    }

    //指定位置插入
    public void addByIndex(E e,int index){
        //先判断输入位置是否合法,插入位置范围为[1,Length+1]
        if (index >= 1 && index <= this.Length+1) {
            //data节点用来接收数据
            Node<E> data = new Node<>(e);
            //temp节点用来当作移动指针
            Node<E> temp = this.HeadNode;
            int len = 0;
            //找到插入位置的前一个结点
            while (len < index - 1) {
                temp = temp.Next;
                len++;
            }

            data.Next = temp.Next;
            temp.Next = data;
            this.Length++;
        } else {
            System.out.println("插入位置异常!");
        }
    }

    /*
        查找链表中第i个数据的元素值
     */
    public E FindByIndex(int i){
        int len = 1;
        //确保输入位置不会越界并且不会为0或者负数
        if (i > this.Length || len > i){
            System.out.println("输入异常!");
            return null;
        }

        //定义移动指针
        Node<E> temp = this.HeadNode.Next;
        while (len < i){
            temp = temp.Next;
            len++;
        }

        return temp.Data;
    }

    /*
        按值查找链表中对应元素的
     */
    public int FindByElement(E e){
        Node<E> temp = this.HeadNode.Next;
        int len = 1;
        //从第一个节点开始判断节点中的数据和输入的是否相当
        while (temp.Data != e){
            if (len == this.Length){
                len = -1;
                break;
            }
            temp = temp.Next;
            len++;
        }
        return len;
    }

    /*
        按指定位置删除
     */
    public void DeleteByIndex(int index){
        if (index >= 1 && index <= this.Length){
            int len = 0;
            Node<E> temp = this.HeadNode;
            //找到删除节点的前一个结点
            while (len < index-1){
                temp = temp.Next;
                len++;
            }

            temp.Next = temp.Next.Next;
            this.Length--;
        }else {
            System.out.println("删除位置异常!");
        }
    }

    /*
        打印链表中所有数据
     */
    public void ListPrint(){
        Node<E> temp = this.HeadNode.Next;
        while (temp != null){
            System.out.print(temp.Data+" ");
            temp = temp.Next;
        }
        System.out.println();
    }

    public int listSize(){
        return this.Length;
    }


}

2 单链表练习题

2.1 链表合并

将两个有序(非递减)单链表La和Lb合并成为一个有序(非递减)单链表
非递减:允许相等的递增

public class ListCombine {
    /*
        将两个有序(非递减)单链表La和Lb合并成为一个有序(非递减)单链表
        非递减:允许相等的递增

        解题思路:
        链表的合并不需要再创建新空间,只需要把两个链表中的节点按非递减顺序串联起来
        单链表头指针不可移动
     */
    public static void MergeList(SinglyLinkedList<Integer> La, SinglyLinkedList<Integer> Lb){
		//temp为移动指针,用来连接合并后的新链表
        SinglyLinkedList.Node<Integer> temp = La.getHeadNode();
        //p负责将La链表串起来
        SinglyLinkedList.Node<Integer> p = La.getHeadNode().Next;
        //q负责将Lb链表串起来
        SinglyLinkedList.Node<Integer> q = Lb.getHeadNode().Next;
        //合并后新链表的长度为原来两链表长度之和
        int len = La.listSize()+Lb.listSize();
        //当p和q节点都不为空时,即La和Lb都没有遍历完成时
        while (p!=null && q!=null) {
            //判断p和q的大小,temp指向小的那个节点,小的那个节点向后移一位
            if (p.Data <= q.Data) {
                temp.Next = p;
                temp = temp.Next;
                p = p.Next;
            } else {
                temp.Next = q;
                temp = temp.Next;
                q = q.Next;
            }
        }

        //当p和q中有一个节点为空时,终止循环
        //此时判断那个节点为空,temp指向非空的那个节点
        temp.Next = p!=null?p:q;
      

        La.setLength(len);
    }
//测试类
    public static void main(String[] args) {
        SinglyLinkedList<Integer> la = new SinglyLinkedList<>();
        SinglyLinkedList<Integer> lb = new SinglyLinkedList<>();
        la.addLast(3);
        la.addLast(8);
        la.addLast(9);
        la.addLast(15);
        la.addLast(20);
        lb.addLast(5);
        lb.addLast(7);
        lb.addLast(13);
        lb.addLast(15);
        lb.addLast(19);
        lb.addLast(22);
        la.ListPrint();
        lb.ListPrint();
        MergeList(la,lb);
        la.ListPrint();
        System.out.println(la.listSize());
    }
}

2.2 查找中间节点

带有头节点的单链表L,设计一个高效的算法求L的中间节点

public class MidSearchList {
    /*
        带有头节点的单链表L,设计一个高效的算法求L的中间节点

        解题思路:
        快慢指针,快指针一次走两步,慢指针一次走一步,当快指针走向末尾时慢指针刚好到中点
     */

    public static SinglyLinkedList.Node MidNodeSearch(SinglyLinkedList<Integer> L){
        SinglyLinkedList.Node f = L.getHeadNode();
        SinglyLinkedList.Node s = L.getHeadNode();
        //循环条件快指针不为0放前面,因为当链表长度为奇数时快指针最后为空,f.next会报空指针异常
        while (f!=null && f.Next!=null){
            f = f.Next.Next;
            s = s.Next;
        }
        return s;
    }

    public static void main(String[] args) {
        SinglyLinkedList<Integer> l = new SinglyLinkedList<>();
        l.addLast(2);
        l.addLast(3);
        l.addLast(4);
        l.addLast(5);
        l.addLast(6);
        System.out.println(MidNodeSearch(l).Data);
    }
}

2.3 链表绝对值去重

设计一个高时间复杂度的算法,对链表中data的绝对值相等的节点
仅保留第一次出现的节点而删除其余绝对值相等的节点

import java.util.HashMap;

public class RemoveDuplicates {
    /*
        设计一个高时间复杂度的算法,对链表中data的绝对值相等的节点
        仅保留第一次出现的节点而删除其余绝对值相等的节点
     */
    //自定义一个absolute方法取绝对值
    public static int absolute(int num){
        return num >= 0 ? num : -num;
    }

    public static void listDuplicatesRemove(SinglyLinkedList L){
        //用map来存链表中数据出现过的绝对值
        HashMap<Integer,Integer> map = new HashMap<>();
        //temp用来连接新链表
        SinglyLinkedList.Node temp = L.getHeadNode();
        //p用来串起旧链表
        SinglyLinkedList.Node<Integer> p = L.getHeadNode().Next;
        int len = 0;
        //遍历原来链表
        while (p!=null){
           //如果map里有绝对值重复的key直接跳过
           //如果没有则将该绝对值放进map并让temp指向该p节点
           if (!map.containsKey(absolute(p.Data))){
               map.put(absolute(p.Data),1);
               temp.Next = p;
               temp = temp.Next;
               len++;
           }
           p = p.Next;
        }
        //让新链表的尾节点指向空,因为可能出现该节点与之后节点绝对值相等的情况
        temp.Next = null;
        L.setLength(len);
    }

    public static void main(String[] args) {
        SinglyLinkedList<Integer> l = new SinglyLinkedList<>();
        l.addLast(2);
        l.addLast(3);
        l.addLast(3);
        l.addLast(5);
        l.addLast(-2);
        l.addLast(2);
        l.addLast(6);
        l.addLast(6);
        l.addLast(3);
        l.addLast(7);
        l.addLast(1);
        l.addLast(-8);
        l.addLast(8);
        l.ListPrint();
        listDuplicatesRemove(l);
        l.ListPrint();
        System.out.println(l.listSize());
    }
}

3 Java实现静态链表

public class staticSingleLinkedList {
    /*
        自定义静态单向链表
     */
    //表示链表的实际长度 - 即链表中保存了几个数据
    private int listLen = 0;
    //用户输入的初始化链表长度 - 数组可用的长度
    private  int Length;
    //用来记录Data数组存到了哪个位置
    //Data数组存放数据按顺序放入,size自增
    private int size=0;
    //存放数据
    private  Object[] Data;
    //存放位置
    private int[] Right;

	//有参构造,给定链表初始长度值
	//初始化链表长度,存数据的Data数组,保存位置的Right数组
    public staticSingleLinkedList(int length) {
        Length = length;
        Data = new Object[length];
        Right = new int[length];
    }

    public void addByLast(Object obj){
        //判断Data数组容量是否达到80%,如果是则扩容
        //减一是因为Data数组存入数据从下标1开始
        if (size >= Length*0.8-1){
            arrayExpanding(size);
        }
        //因为Data数组下标0的位置表示头节点不存数据,数据从下表1开始存入
        Data[size+1] = obj;
        //将前Data数组中前一个节点size指向插入节点size+1
        Right[size] = size+1;
        //更新Data数组插入位置
        size++;
        listLen++;
    }

    public void addByFirst(Object obj){
        //判断是否需要扩容
        if (size >= Length*0.8-1){
            arrayExpanding(size);
        }
        //Data数组按顺序插入
        Data[size+1] = obj;
        //因为为头插法,新插入数据放在头节点即Right[0]后
        //新插入节点的下一个节点为原来0位置节点的下一个节点
        Right[size+1] = Right[0];
        //0位置节点的下一个节点为新插入节点
        Right[0] = size+1;
        size++;
        listLen++;
    }

    public void addByIndex(int index,Object obj){
        //判断输入下标是否合理
        if (index<1||index>listLen+1){
            System.out.println("下标输入错误");
            return;
        }
        //判断是否需要扩容
        if (size >= Length*0.8-1){
            arrayExpanding(size);
        }
        Data[size+1] = obj;
        //找到插入位置节点的前一个结点,从头节点开始
        int j = 0;
        for (int i = 1; i < index; i++) {
            j = Right[j];
        }
        //新插入节点指向插入位置的节点即找到的前一个节点的后一个节点
        Right[size+1] = Right[j];
        //插入节点的前一个节点指向新插入节点
        Right[j] = size+1;
        size++;
        listLen++;
    }

    //根据输入下标查找
    public Object searchByIndex(int index){
        //判断输入位置是否合理
        if (index<1||index>listLen){
            throw new IndexOutOfBoundsException("下标越界!");
        }
        //找到输入下标位置的前一个结点
        int j = 0;
        for (int i = 1; i < index; i++) {
            j = Right[j];
        }
        //返回查找位置的值 - Right[j]为找到的前一个节点的后一个节点即查找位置节点
        return Data[Right[j]];
    }

    //按元素值查找
    public int searchByData(Object obj){
        //遍历循环整个链表
        int j = 0;
        for (int i =0; i < listLen; i++) {
            //如果找到该元素则返回结束
            if (Data[Right[j]] == obj){
                return i+1;
            }else {
                //值不相同则接着比较下一个元素
                j = Right[j];
            }
        }
        //遍历完了都没有找到则不存在,反回-1
        return -1;
    }

    public void deleteByIndex(int index){
        //判断输入位置是否合理
        if (index<1||index>listLen){
            throw new IndexOutOfBoundsException("下标越界!");
        }
        int j = 0;
        //扎到删除节点的前一个结点
        for (int i = 1; i < index; i++) {
            j = Right[j];
        }
        //删除节点的前一个节点直接指向删除节点的后一个节点
        Right[j] = Right[Right[j]];
        //链表长度减一
        listLen--;
    }

    public void listPrint(){
        int j = 0;
        for (int i = 1; i <= listLen; i++) {
            System.out.print(Data[Right[j]]+" ");
            j = Right[j];
        }
        System.out.println();
    }

   //数组自动扩容
   //新建两个等长的数组arr,arr1拷贝Data,Right的数据
   //对Data,Right扩容1.5倍并重新初始化
   //再将arr,arr1的数据拷贝回扩容后的数组
   public void arrayExpanding(int count){
        Object[] arr = new Object[count+1];
        System.arraycopy(Data,0,arr,0,count+1);
        Data = new Object[Data.length+(Data.length>>1)];
        System.arraycopy(arr,0,Data,0,count+1);
        int[] arr1 = new int[count+1];
        System.arraycopy(Right,0,arr1,0,count+1);
        Right = new int[Data.length+(Data.length>>1)];
        System.arraycopy(arr1,0,Right,0,count+1);
        Length = Data.length;
   }

    public static void main(String[] args) {
        staticSingleLinkedList list = new staticSingleLinkedList(5);
        list.addByLast(1);
        list.addByLast(2);
        list.addByLast(3);
        list.addByLast(4);
        list.addByLast(5);
        list.addByLast(6);
        list.addByFirst(7);
        list.listPrint();
        list.addByIndex(2,8);
        list.listPrint();
        list.addByIndex(1,9);
        list.listPrint();
        list.addByIndex(9,10);
        list.listPrint();
        list.addByIndex(11,11);
        list.listPrint();
        //System.out.println(list.searchByIndex(12));
        //System.out.println(list.searchByData(15));
        list.deleteByIndex(3);
        list.listPrint();
        list.deleteByIndex(1);
        list.listPrint();
        list.deleteByIndex(9);
        list.listPrint();
    }
}
posted @ 2020-11-11 11:02  Pengc931482  阅读(183)  评论(0)    收藏  举报