数据结构学习--链表(JAVA代码实现)

线性表的定义

线性表(List):零个或者多个具有相同类型的数据元素的有限序列。

若将线性表记为(a1,…,ai-1,ai,ai+1,…,an)的话。则表中ai-1领先于ai,而ai领先于ai+1。故称ai-1是ai的直接前驱元素,ai+1是ai的直接后继元素。如图所示。
如图所示
n=0时候,称为空表。

同时,在较为复杂的线性表中,一个数据元素可以由若干个数据项组成。就如下表一样
在这里插入图片描述
他们必须要有相同的数据类型。

同时,再聊一聊线性表的抽象数据类型。个人理解,就是对线性表的基本操作。

主要操作
~删除:移除并返回表中指定位置的元素
~插入:插入一个元素到表中
辅助操作
~删除表:清空表中所有元素
~计数:返回表中的元素个数
~查找:寻找从表表尾开始的第n个结点(node)

不多BB,先上线性表的两种结构的分类图
在这里插入图片描述
这里值得注意一下:线性表 != 链表, 或者说的是 链表属于线性表的一种分类。

线性表的顺序存储结构(数组)

指的是用一段地址连续的存储单元依次存储线性表的数据元素。通常使用数组来实现。

在这里插入图片描述
数组长度是固定的,线性表的长度不固定,线性表的当前长度不得大于数组长度,否则会报错。或者动态扩容,但是这样操作会有性能上的损耗。

为了代码方便,这里直接将数据类型定义为int类型

插入操作

//插入操作

删除操作

//删除操作

查找操作

//查找操作

好了,写到这里,因为顺序存储结构两个元素的存储位置具有“相邻”的邻居关系,所以需要链表来解决。
该讲讲接下来的重点:链表。

单向链表

链表同行指的是单向链表,包含多个结点,每个结点都有指向后继元素的next指针(下一个指针)。最后一个结点指针值为Null,代表着链表的结束。
单向列表
链表的类型声明代码:

public class ListNode{
	private int data;
	private ListNode next;
	public ListNode(int data){
		this.data = data;
	}
	//setter,getter方法
}

遍历

沿着指针遍历,遍历时候显示结点的内容,next指针值为null时候则结束遍历。
下面代码为统计个数。

int ListLength(ListNode headNode){
	int length = 0;
	ListNode currentNode = headNode;
	while(currentNode!= null){
		length++;
		System.out.pritln("data>>>>>>"+currentNode.getData());
		currentNode = currentNode.getNext();
	}
}


    @Test
    public void test(){
        ListNode listNode = new ListNode();
        ListNode listNode2 = new ListNode();
        listNode.setData(111);
        listNode2.setData(222);
        listNode.setNext(listNode2);
        listNode2.setNext(null);
        System.out.println("listLength>>>>>>>>>>>>>"+ListLength(listNode));
    }
   // 输出结果为
   //data>>>>111
   //data>>>>222
   //listLgenth>>>>>>2

插入操作

思路如下

  1. 若在单向链表的开头插入结点
    只需要修改一个next指针(新结点的next指针),即可完成。在这里插入图片描述

  2. 若在单向链表的结尾插入结点
    修改两个指针(最后一个结点的next指针和新结点的next指针)
    在这里插入图片描述

  3. 若在单向链表的中间插入结点
    修改两个指针
    在这里插入图片描述在这里插入图片描述

ListNode InsertInLinkedList(ListNode headNode, ListNode nodeToInsert, int position){
        //链表为空,直接插入
        if(headNode==null){
            return nodeToInsert;
        }

        int size = ListLength(headNode);
        //判断插入位置合法与否
        if(position>size+1 ||position<1){
            System.out.println("插入位置不合法,合法插入位置是 1到"+(size+1));
            return headNode;
        }
        //在链表开头插入
        if(position ==1){
            nodeToInsert.setNext(headNode);
            return nodeToInsert;
        }else {
            
            //在中间或者末尾插入
            ListNode previousNode = headNode;
            int count = 1;
            
            //知道位置在哪里插入,获得到插入位置的前一个结点
            while (count< position-1){
                previousNode = previousNode.getNext();
                count++;
            }
            
            //获得插入结点的后一个结点
            ListNode currentNode = previousNode.getNext();
            
            //需要插入的结点的下一个结点自然而然就是currentNode
            nodeToInsert.setNext(currentNode);
            
            //同理
            previousNode.setNext(nodeToInsert);
        }
        return headNode;
    }

删除操作

    ListNode deleteFromLinkedList(ListNode headNode, int position){
        int size = ListLength(headNode);
        //判断删除位置合法与否
        if(position>size ||position<1){
            System.out.println("插入位置不合法,合法插入位置是 1到"+(size));
            return headNode;
        }
        //第一个删除
        if(position ==1){
            ListNode currentNode = headNode.getNext();
            headNode = null;
            return currentNode;
        }else{
            //结尾或中间删除
            ListNode preNode  = headNode;
            int count = 1;
            while (count < position -1 ){
                preNode = preNode.getNext();
                count++;
            }
            //这个就是被删的了
            ListNode currentNode = preNode.getNext();
            preNode.setNext(currentNode.getNext());
            currentNode=null;
        }
        return headNode;
    }

    @Test
    public void testDeleteOne(){
        ListNode listNode1 = new ListNode();
        ListNode listNode2 = new ListNode();
        ListNode listNode3 = new ListNode();
        ListNode listNode4 = new ListNode();
        listNode1.setData(11111);
        listNode2.setData(22222);
        listNode3.setData(33333);
        listNode4.setData(44444);
        listNode1.setNext(listNode2);
        listNode2.setNext(listNode3);
        listNode3.setNext(listNode4);
        listNode4.setNext(null);
//        System.out.println(deleteFromLinkedList(listNode1, 1));
//        System.out.println(">>>>>>>>>>>");
        System.out.println(listNode1);
        System.out.println(deleteFromLinkedList(listNode1, 2));
        System.out.println(">>>>>>>>>>>>>");
    }




查找操作

    ListNode findNode(int position,ListNode headNode){
        if(headNode==null){
            System.out.println("链表为空");
            return null;
        }
        int size  = ListLength(headNode);
        if(position<1 || position>size){
            System.out.println("位置不合法,合法位置是 1到"+(size));
            return null;
        }
        int count =1;
        ListNode currentNode = headNode;
        while(count< position){
            currentNode = currentNode.getNext();
            count++;
        }
        return currentNode;
    }

    @Test
    public void testFindNode(){
        ListNode listNode1 = new ListNode();
        ListNode listNode2 = new ListNode();
        ListNode listNode3 = new ListNode();
        ListNode listNode4 = new ListNode();
        listNode1.setData(11111);
        listNode2.setData(22222);
        listNode3.setData(33333);
        listNode4.setData(44444);
        listNode1.setNext(listNode2);
        listNode2.setNext(listNode3);
        listNode3.setNext(listNode4);
        listNode4.setNext(null);

        System.out.println(findNode(1,listNode1));
        System.out.println(findNode(2,listNode1));
    }

双向链表

故名思议,双向链表,就是双向的链表。
他的主要优点是:

  1. 可以从两个方向进行操作,不同于单向列表那样只有获得前驱结点的指针,才能删除该结点。

主要的缺点也是有的:

  1. 每个结点要多个额外的指针,需要更多的空间。
  2. 插入删除需要更多的操作

类型声明代码

public class DLLNode{
        private int data;
        private DLLNode next;
        private DLLNode previous;
        private DLLNode(int data){
            this.data = data;
        }

        @Override
        public String toString() {
            return "DLLNode{" +
                    "data=" + data +
                    ", next=" + next +
                    '}';
        }

        public int getData() {
            return data;
        }

        public void setData(int data) {
            this.data = data;
        }

        public DLLNode getNext() {
            return next;
        }

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

        public DLLNode getPrevious() {
            return previous;
        }

        public void setPrevious(DLLNode previous) {
            this.previous = previous;
        }
    }

遍历操作

    int getDLLNodeLength(DLLNode headNode){
        int length = 0;
        DLLNode currentNode = headNode;
        while(currentNode!=null){
            length++;
            currentNode = currentNode.getNext();
        }
        return length;
    }

    @Test
 public void testDLLNOdeLength(){
        DLLNode node1 = new DLLNode(111);
        DLLNode node2 = new DLLNode(222);
        DLLNode node3 = new DLLNode(333);
        DLLNode node4 = new DLLNode(444);

        node1.setPrevious(null);
        node1.setNext(node2);

        node2.setPrevious(node1);
        node2.setNext(node3);

        node3.setPrevious(node2);
        node3.setNext(node4);

        node4.setPrevious(node3);
        node4.setNext(null);

        System.out.println(getDLLNodeLength(node1));//4
    }

插入操作

   DLLNode DLLNodeInsert(DLLNode headNode,DLLNode needToInsertNode, int position){
        if(headNode ==null){
            return needToInsertNode;
        }
        int size = getDLLNodeLength(headNode);
        System.out.println("链表长度="+size);
        if(size+1<position || position<1){
            System.out.println("插入位置错误,可插入的位置为1到"+(size+1));
            return headNode;
        }

        if(position==1){
            needToInsertNode.setPrevious(null);
            needToInsertNode.setNext(headNode);
            headNode.setPrevious(needToInsertNode);
            return needToInsertNode;
        }else {
            DLLNode preNode = headNode;
            int count =1 ;
            while(count<position-1){
                preNode = preNode.getNext();
                count++;
            }

            DLLNode currentNode = preNode.getNext();

            needToInsertNode.setNext(currentNode);
            if(currentNode!=null){
                currentNode.setPrevious(needToInsertNode);
            }
            preNode.setNext(needToInsertNode);
            needToInsertNode.setPrevious(preNode);
        }
        return headNode;
    }


删除操作

    DLLNode DLLNodeDelete(DLLNode headNode,int position){
        int size = getDLLNodeLength(headNode);
        if(position>size || position<1){
            System.out.println("Invaild size,the right size should be 1 to "+size);
            return headNode;
        }
        if(position==1){
            DLLNode currentNode = headNode.getNext();
            currentNode.setPrevious(null);
            headNode= null;
            return currentNode;
        }else {
            DLLNode previousNode = headNode;
            int count = 1;
            if(count<position-1){
                count++;
                previousNode =previousNode.getNext();
            }
            //得出被删除的结点
            DLLNode currentNode = previousNode.getNext();
            //如果被删除的结点不是最后一个的话,即是中间
            if(currentNode.getNext()!=null){
                //让被删除结点的前驱结点 指向 被删除结点的后驱结点
                previousNode.setNext(currentNode.getNext());
                //被删除结点的后驱结点也指向被删除的前驱结点
                currentNode.getNext().setPrevious(previousNode);
                currentNode=null;
            }else {
                previousNode.setNext(null);
                currentNode=null;
            }

        }
        return headNode;
    }

循环链表

在这里插入图片描述
直接上优缺点吧:

  1. 优点,遍历灵活,没有null,直接从任意结点遍历都可
  2. 缺点,查找慢

结构代码又如单向链表一样

 public class CircularList{
        private int data;
        private CircularList next;

        @Override
        public String toString() {
            return "CircularList{" +
                    "data=" + data +
                    ", next=" + next +
                    '}';
        }

        public int getData() {
            return data;
        }

        public void setData(int data) {
            this.data = data;
        }

        public CircularList getNext() {
            return next;
        }

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

遍历操作

    int findCircularListLength(CircularList headNode){
        int length = 0;
        CircularList currentNode = headNode;
        while(currentNode!=null){
            length++;
            currentNode = currentNode.getNext();
            if(currentNode==headNode){
                break;
            }
        }
        return length;
    }

插入操作


    void insertIntoEndCLL(CircularList headNode,CircularList needToInsertNode){
        CircularList currentNode = headNode;
        while(currentNode.getNext()!=headNode){
            currentNode.setNext(currentNode.getNext());
        }
        //容易理解,当currentNode.getNext==headNode时,currentNode就是尾结点了
        needToInsertNode.setNext(needToInsertNode);
        if(headNode==null){
            headNode =needToInsertNode;
        }else {
            needToInsertNode.setNext(headNode);
            currentNode.setNext(needToInsertNode);
        }
    }

    void insertIntoBeginCLL(CircularList headNode,CircularList needToInsertNode){
        CircularList currentNode = headNode;
        while(currentNode.getNext()!=headNode){
            currentNode.setNext(currentNode.getNext());
        }
        needToInsertNode.setNext(needToInsertNode);
        if(headNode==null){
            headNode =needToInsertNode;
        }else {
            needToInsertNode.setNext(headNode);
            currentNode.setNext(needToInsertNode);
            //直接把头结点变成需要插入的即可了。。。
            headNode=needToInsertNode
        }

删除操作

void deleteLastNodeFromCircularList(CircularList headNode){
        CircularList temp = headNode;
        CircularList currentNode = headNode;
        if(headNode==null){
            System.out.println("链表为空");
            return;
        }
        while (currentNode.getNext()!=headNode){
            temp = currentNode;
            currentNode = currentNode.getNext();
        }
        temp.setNext(headNode);
        currentNode = null;
        return;
    }




void deleteFrontNodeFromDLL(CircularList headNode){
        CircularList temp = headNode;
        CircularList current = headNode;
        if(headNode==null){
            System.out.println("List Empty");
            return;
        }
        while (current.getNext()!=headNode)
            current.setNext(current.getNext());
        current.setNext(headNode.getNext());
        headNode = headNode.getNext();
        temp = null;
        return;
    }

总结

顺序存储(数组)的优点:

  1. 无须为表中元素之间的逻辑而增加额外的存储空间。
  2. 可以快速的存取表中任何一位置的元素(查找快,常数时间)。

顺序存储(数组)的缺点:

  1. 插入和删除操作需要移动大量元素。
  2. 线性表长度变化较大的时候,难以确定存储空间的容量。
  3. 造成存储空间的“碎片”。

链式存储(链表)的优点:

  1. 可以在常数时间内扩展,不同于数组的麻烦扩展(新建数组,把原数组的元素复制进去,新建的的数组大小也是一个迷…)。链表可以简单,仅仅多分配一个元素的存储空间。

链式存储(链表)的缺点:

  1. 访问单个元素时候过慢,若元素在第一个,则时间复杂度为O(1),若在末尾,则为O(n)。
  2. 而数组因为是连续的内存块,任何数组元素都是物理相连的,故比他快。
  3. 额外指针引用浪费内存。

附上一个链表,数组,动态数组的比较
在这里插入图片描述

本人学习的理解笔记,如有错误!不吝赐教,请留言,谢谢你!

猜你喜欢

转载自blog.csdn.net/weixin_42553616/article/details/106689089