线性表的原理与实现

作者: 愤怒的谜团 | 来源:发表于2019-11-10 17:55 被阅读0次

    一、线性表的顺序存储结构

    1.线性表(List)的定义

    由零个或多个数据元素组成的有限序列,数据元素类型可以是基本类型,也可以是抽象类型class,把它想象成一个结点就行。

    2. 线性表的顺序存储

    顺序存储是由在内存当中一块连续的空间组成的,这块内存空间,在创建线性表时就需要确定大小,这一点刚好对应了java当中的数组,java创建一个数组,会返回一个引用,数组元素存于堆内存当中,是一块连续的空间,引用是存于栈当中,使用时push进栈。


    顺序存储.png

    3. 顺序存储的优势

    存取速率快,时间复杂度为O(1),因为数据元素存于地址连续的内存空间当中,每一个小结点,分配的大小是固定的,比如int分配8字节,那么很快可以根据存取的脚标计算出结点的位置。并且顺序存储不需要开辟空间去存储下一个结点的引用,节省了内存开销。

    4.顺序存储的劣势

    增删结点会有较多不便,想象一下,最好的情况是在线性表的最后一个结点进行增删,那么时间复杂度是O(1),因为其他数据元素不需要进行位移。最糟糕的情况,在线性表的第一个结点进行增删,那么时间复杂度是O(n),n被称作问题规模,简单理解为数据量,平均一下,时间复杂度也有(n+1)/2。

    二、线性表的链式存储结构

    1. 线性表的链式存储

    这种方式底层不在是采用数组的方式,因为在链式存储方式下,相邻的两个数据元素存储的位置不在连续的内存空间,那如何体现线性表的有序呢?每个数据元素除了存储元素值以外,还需要额外开辟空间来存储指向下个数据元素的引用,这样就可以将不同结点链接起来。


    链式存储.png

    2.链式存储的优势

    之前用顺序存储的模式,在进行增删的时候,只要不是最后一个结点,就会产生大量数据元素的位移,用链式存储可以很好的解决这个问题,因为链式存储只要修改一下引用的指向就可以了,不需要位移任何元素,时间复杂度为O(1),但是这里有一点,就是遍历查找需要增删数据元素的复杂度,也为O(n),但是对于批量增删来说,链式存储还是有很明显的优势的。并且链式存储不需要一开始就固定线性表的大小,即用即申请,非常方便。

    3.链式存储的劣势

    链式存储需要为每一个结点单独开辟一个内存空间来存储引用。并且由于每个数据元素存储的内存位置不是连续的,存取时遍历带来的时间复杂度为O(n)。

    三、顺序存储的实现-java

    import java.util.ArrayList;
    
    public class MyArrayList {
    
        /**
         * 线性表反应的是若干数据元素之间的线性关系,与树不同,元素之间的关系都是一对一的
         * 线性表存在两种存储方式:顺序存储,链式存储
         */
        /**
         * 线性表一般具有以下功能:
         * 1.InitList(int Maxlenth) 初始化操作,建立一个空的线性表
         * 2.ListEmpty() 判断线性表是否为空,为空则返回true,非空返回false
         * 3.ClearList() 将线性表清空
         * 4.GetElem(int index) 返回位置index的元素
         * 5.LocateElem(T elem) 返回与给定elem值相等的元素,返回第一个相同值的脚标
         * 6.ListInsert(T elem) 在线性表末尾插入数据,不需要手动指定位置
         * 7.ListInsertWithIndex(int index,T elem) 在线性表脚标为index处插入新元素
         * 8.ListDelete(int index) 删除线性表脚标为index的元素,并返回其值
         * 9.ListLenth() 返回当前线性表的长度
         * 10.toString() 打印当前线性表的所有元素值,用逗号分隔
         */
    
        /**
         * 使用顺序存储的方式来实现一个线性表,即java数组。
         */
        int MaxLenth; //定义该线性表最大存储空间
        int Currlength = 0; //定义该线性表当前大小
        int[] myArrayList = null;
    
        //实现InitList功能
        public MyArrayList(int Maxlenth){
            // 以整型数组为例
            myArrayList = new int[Maxlenth];
            this.MaxLenth = Maxlenth;
        }
    
        //实现ListEmpty功能
        public Boolean ListEmpty(){
            return Currlength == 0?true:false;
        }
    
        //实现ClearList功能
        public int[] ClearList(){
            /**
             * java当中没有手动操作内存的API,不想C语言当中的指针,但是当一块内存没有引用
             * 指向时,就会开始等待GC(JVM的垃圾回收机制)
             */
            return new int[MaxLenth];
        }
    
        //实现GetElem的功能
        public int GetElem(int index){
            if (index < 0 || index >= Currlength){return -1;}
            else {return myArrayList[index];}
        }
    
        //实现LocateElem(T elem)功能
        public int LocateElem(int elem){
            for (int head = 0;head < Currlength;head++){
                if (myArrayList[head] == elem){return head;}
                else {continue;}
            }
            return -1;//返回-1表示未找到有与其对应的元素值
        }
    
        //实现ListInsert(int index,T elem)功能
        public Boolean ListInsert(int elem){
            if (Currlength == MaxLenth){return false;}
            myArrayList[Currlength++] = elem;
            return true;
        }
    
        //ListInsertWithIndex(int index,T elem)功能
        public Boolean ListInsertWithIndex(int index,int elem){
            /**
             * 插入操作会相对复杂一些,由于数组这种顺序结构,插入会导致
             * 被插元素后面的所有元素需要后移一位
             */
            if (Currlength == MaxLenth){return false;}
    
            if (Currlength == 0){myArrayList[0] = elem; Currlength++;return true;}
    
            for (int rear = Currlength;rear >= index;rear--){
                    myArrayList[rear] = myArrayList[rear-1];
            }
            myArrayList[index] = elem;
            Currlength++;
            return true;
        }
    
        //实现ListDelete(int index)功能
        public int ListDelete(int index){
            /**
             * 与插入操作相反,删除操作需要将被删元素后面所有元素都向前移一位
             */
            if (Currlength == 0 ){return -1;}
            int returnTempElem = -1;
            for (int start = index;index < Currlength;index++){
                returnTempElem = myArrayList[index];
                myArrayList[index] = myArrayList[index+1];
            }
            Currlength--;
            return returnTempElem;
        }
    
        //实现ListLenth()的功能
        public int ListLenth(){
            return Currlength;
        }
    
        //实现toString方法
        public String toString(){
            StringBuffer returnTempString = new StringBuffer();
            for (int start = 0;start<Currlength;start++){
                if (start != Currlength-1){
                    returnTempString.append(myArrayList[start] + ",");
                }else {
                    returnTempString.append(myArrayList[start]);
                }
            }
            return returnTempString.toString();
        }
    
        public static void main(String[] args) {
        }
    }
    
    

    四、链式存储的实现-java

    public class MyLinkedList {
        /**
         * 线性表反应的是若干数据元素之间的线性关系,与树不同,元素之间的关系都是一对一的
         * 线性表存在两种存储方式:顺序存储,链式存储
         */
        /**
         * 线性表一般具有以下功能:
         * 1.InitList() 初始化操作,建立一个空的线性表
         * 2.LinkedListEmpty() 判断线性表是否为空,为空则返回true,非空返回false
         * 3.ClearLinkedList() 将线性表清空
         * 4.GetLinkedElem(int index) 返回位置index的元素
         * 5.LocateLinkedElem(T elem) 返回与给定elem值相等的元素,返回第一个相同值的脚标
         * 6.LinkedListInsert(T elem) 在线性表末尾插入数据,不需要手动指定位置
         * 7.LinkedListInsertWithIndex(int index,T elem) 在线性表脚标为index处插入新元素
         * 8.LinkedListDelete(int index) 删除线性表脚标为index的元素,并返回其值
         * 9.LinkedListLenth() 返回当前线性表的长度
         * 10.toString() 打印当前线性表的所有元素值,用逗号分隔
         */
    
        /**
         * 使用链式存储的方式来实现一个线性表
         */
        private int CurrLenth = 0; //线性表的大小
        private LinkedNode headNode = null; // 头结点
        private LinkedNode rear = null; //尾指针,方便线性表尾部的操作
    
        class LinkedNode{
            String nodeData; //存储节点数据
            LinkedNode next; //存储指向下一个节点的位置
        }
    
        public MyLinkedList(){
            /**
             * 初始化一个链式存储的线性表,生成头结点,尾指针
             */
            this.headNode = new LinkedNode();
            headNode.nodeData = null; //头结点的数据域不需要存储数据
            headNode.next = null; //初始化一个链式存储的线性表,由于没有第一个结点,所以头结点的next为空
            this.rear = headNode; //将尾部指针指向头结点
        }
    
        //实现LinkedListEmpty的功能
        public Boolean LinkedListEmpty(){
            //头结点默认不算长度
            return CurrLenth == 0?true:false;
        }
    
        //实现ClearLinkedList的功能
        public Boolean ClearLinkedList(){
            /**
             * 由于java不能手动释放内存占用,只能依靠GC机制,所以这边需要循环
             * 将每个指向结点的引用置为空,这样当一块内存没有引用指向时,JVM
             * 会自动回收。
             */
            if (CurrLenth == 0){return true;} //空链表,直接返回true
            LinkedNode index = headNode.next; //该游标作用是保存释放结点前,存放该结点的指针域数据
            LinkedNode currIndex = headNode.next; //该游标作用是指向待释放结点对象
    //        headNode.next = null; //释放头结点对一个结点的链接
            if (CurrLenth == 1){
                //表示只有一个结点时
                index = null;
                currIndex = null;
                headNode.next = null;
                // 释放完以后的初始化操作
                CurrLenth = 0;
                rear = headNode;
                return true;
            }
            for (;index.next != null;){
                index = currIndex.next;
                currIndex.next = null;
                currIndex = index;
            }
            // 释放临时游标指针
            index = null;
            currIndex = null;
            // 释放头结点链接
            headNode.next = null;
            // 释放完以后的初始化操作
            CurrLenth = 0;
            rear = headNode;
            return true;
        }
    
        //实现GetLinkedElem的功能
        public String GetLinkedElem(int index){
            if (index <= 0 || index > CurrLenth){return "error";}
            // 假设头结点为index=0,第一个结点index=1
            LinkedNode currIndex = headNode.next;
            for (int curr = 1;curr <= index;curr++,currIndex = currIndex.next){
                if (curr == index){
                    return currIndex.nodeData;
                }else{
                    continue;
                }
            }
            return "error";
        }
    
        //实现LocateLinkedElem的功能
        public int LocateLinkedElem(String elem){
            if (CurrLenth == 0){return -1;}
            LinkedNode start = headNode.next;
            int index = 1;
            for(;start != null;start = start.next,index++){
                if (start.nodeData.equals(elem)){return index;}
                else{
                    continue;
                }
            }
            return -1;
        }
    
    
        //实现LinkedListInsert的功能
        public Boolean LinkedListInsert(String elem){
            /**
             * 要插入一个新的结点,需要考虑什么,跟顺序存储不同,链式存储可以称得上有
             * 无限空间,除非堆内存溢出了,所以不需要判断是否空间不足
             */
            try{
                LinkedNode linkedNode = new LinkedNode();
                linkedNode.nodeData = elem;
                if (headNode.next == null){
                    // 头结点为空,表示插入的是第一个结点
                    headNode.next = linkedNode;
                    rear = linkedNode;
                    CurrLenth++;
                    return true;
                }else{
                    rear.next = linkedNode; //让尾指针指向下一个结点,即将该结点放于线性表最后的位置
                    linkedNode.next = null;
                    rear = rear.next;
                    CurrLenth++;
                    return true;
                }
            }catch(Exception e){
                System.out.println("Add LinkedNode Find unknow Error!"+e);
                return false;
            }
        }
    
        //实现LinkedListInsertWithIndex功能
        public Boolean LinkedListInsertWithIndex(int index,String elem){
            if (index <= 0 || index > CurrLenth+1){
                // 当index = CurrLenth+1时,表示需要插入到链表尾部
                return false;
            }
            int currIndex = 1; //当前脚标
            LinkedNode currNode = headNode.next;
            LinkedNode insertNode = null; //待插入的结点
            if (index == 1){
                // 表示要插入到头结点后面的第一个结点,需要修改头结点
                insertNode = new LinkedNode();
                insertNode.nodeData = elem;
                insertNode.next = currNode;
                headNode.next = insertNode;
                CurrLenth++;
                return true;
            }
            if (index == CurrLenth+1){
                //表示是插入最后结点后面的位置,这种情况需要修改尾指针
                insertNode = new LinkedNode();
                rear.next = insertNode;
                insertNode.nodeData = elem;
                insertNode.next = null;
                rear = insertNode;
                CurrLenth++;
                return true;
            }
            while (currIndex != index-1){
                // 主要是遍历出要插入的前一个结点所处的位置
                currIndex++;
                currNode = currNode.next;
            }
            LinkedNode nextNode = currNode.next;
            insertNode = new LinkedNode();
            insertNode.nodeData = elem;
            currNode.next = insertNode;
            insertNode.next = nextNode;
            CurrLenth++;
            return true;
        }
    
        //实现LinkedListDelete功能
        public String LinkedListDelete(int index){
            if (index <=0 || index > CurrLenth){return "error";}
            int currIndex = 1;
            LinkedNode currNode = headNode.next;
            LinkedNode realseNode = null; //待释放的结点
            String returnString = null; //待返回的字符串
            if (index == 1){
                // 表示要删除头结点后面的位置,操作特殊,需要修改头结点指针域
                if (headNode.next.next != null){
                    // 说明链表不止一个结点,删除第一个结点,不需要修改尾指针
                    returnString = currNode.nodeData;
                    realseNode = currNode;
                    headNode.next = currNode.next;
                    realseNode.next = null;
                    CurrLenth--;
                    return returnString;
                }else {
                    returnString = currNode.nodeData;
                    currNode.next = null;
                    headNode.next = null;
                    rear = headNode;
                    CurrLenth--;
                    return returnString;
                }
            }
            while (currIndex != index-1){
                currIndex++;
                currNode = currNode.next;
            }
            if (currIndex+1 == CurrLenth){
                // 表示删除的是最后一个结点,所以需要修改尾指针
                rear = currNode; // 首先将尾指针指向即将删除的尾结点的前一个结点
                realseNode = currNode.next;
                returnString = realseNode.nodeData;
                currNode.next = null;
                realseNode.next = null;
                CurrLenth--;
                return returnString;
            }else{
                realseNode = currNode.next;
                returnString = realseNode.nodeData;
                currNode.next = realseNode.next;
                realseNode.next = null;
                CurrLenth--;
                return returnString;
            }
        }
    
        //实现LinkedListLenth的功能
        public int LinkedListLenth(){
            return  CurrLenth;
        }
    
        //实现toString的功能
        public String toString(){
            StringBuffer stringBuffer = new StringBuffer();
            if (headNode.next == null){return "";} //表示是空链表
            if (headNode.next != null && headNode.next.next == null){
                // 表示该线性表只存在一个结点
                return headNode.next.nodeData;
            }
            for (LinkedNode start = headNode.next;start != null;start = start.next){
                if (start.next == null){
                    //表示是最后一个结点
                    stringBuffer.append(start.nodeData);
                }else{
                    stringBuffer.append(start.nodeData+",");
                }
            }
            return stringBuffer.toString();
        }
    
        public static void main(String[] args) {
            MyLinkedList myLinkedList = new MyLinkedList();
            myLinkedList.LinkedListInsert("Hello");
            myLinkedList.LinkedListInsert("world");
    
            myLinkedList.LinkedListDelete(1);
    
            System.out.println(myLinkedList.toString());
    
    //        System.out.println(myLinkedList.toString()); // Hello,world
    //        System.out.println(myLinkedList.LinkedListEmpty()); //false
    //        System.out.println(myLinkedList.LinkedListLenth()); // 2
    //        myLinkedList.ClearLinkedList();
    //        System.out.println(myLinkedList.toString()); // 为空
    //        System.out.println(myLinkedList.GetLinkedElem(1)); // hello
    //        System.out.println(myLinkedList.GetLinkedElem(2)); // world
    //        System.out.println(myLinkedList.GetLinkedElem(3));  // error
    //        System.out.println(myLinkedList.GetLinkedElem(-1)); // error
    //        System.out.println(myLinkedList.LocateLinkedElem("Hello")); // 1
    //        System.out.println(myLinkedList.LocateLinkedElem("world")); // 2
    //        System.out.println(myLinkedList.LocateLinkedElem("world1")); // -1
    //        myLinkedList.LinkedListInsertWithIndex(2,"insert");
    //        System.out.println(myLinkedList.toString()); // Hello,insert,world
    //
    //        myLinkedList.LinkedListInsertWithIndex(1,"start");
    //        System.out.println(myLinkedList.toString()); // start,Hello,insert,world
    //
    //        myLinkedList.LinkedListInsertWithIndex(5,"end");
    //        System.out.println(myLinkedList.toString()); // start,Hello,insert,world,end
    //
    //        myLinkedList.LinkedListInsertWithIndex(7,"error");
    //        System.out.println(myLinkedList.toString()); // start,Hello,insert,world,end
    
    //        myLinkedList.LinkedListDelete(1);
    //        System.out.println(myLinkedList.toString()); // Hello
    //
    //        myLinkedList.LinkedListDelete(0);
    //        System.out.println(myLinkedList.toString()); // Hello
        }
    
    }
    
    

    五、两种存储方式时间复杂度分析

    最好情况时间复杂度 最差情况时间复杂度 平均时间复杂度
    顺序存储的存取 O(1) O(1) O(1)
    顺序存储的增删 O(1) O(n) O((n+1)/2)=O(n)
    链式存储的存取 O(1) O(n) O((n+1)/2)=O(n)
    链式存储的增删(去除遍历的开销) O(1) O(1) O(1)

    所以综合来讲,顺序存储的方式适合多存取,少增删,并且能够确定线性表大小的场景。链式存储适合多增删,少存取,无法确定线性表大小的场景。

    代码进行过简单的校验,如果存在bug,望私信指正!

    相关文章

      网友评论

        本文标题:线性表的原理与实现

        本文链接:https://www.haomeiwen.com/subject/abeabctx.html