数据结构之线性表

来源:互联网 发布:any do 软件 编辑:程序博客网 时间:2024/06/09 20:13

数据结构之线性表

理解了数据结构其实就是讨论数据之间的关系,知道算法是什么,如何估算程序的时间复杂度,以及什么是抽象数据类型之后,就可以开始学习前人为我们总结出来的这些典型的数据结构了。

现实生活中的排队就是一个典型的线性表的案例,线性表的抽象数据类型如下:

ADT 线性表 (List)Data    线性表的数据对象集合用数学方法可以这样表示{a1,a2a3,...an,},其中除了第一个元素没有前驱元素,最后一个元素没有后驱元素,内一个元素都是有且只有一个前驱元素和后驱元素。数据元素之间的关系是一对一的关系。(其实这么一大段话就是用文字把线性表这个概念描述出来而已,心里只要想着排队就可以了)OperationInitList();         //初始化操作,建立一个新的线性表 LListIsEmpty();      //判断线性表是否为空ClearList();        //清空线性表GetElem();          //获得 L 中第 i 个元素的值,并返回给 e LocateElem();       //判断 L 中是否有和 e 相同的元素,有就返回1,没有返回0ListInsert();       //在第 i 个位置插入元素 eListDelete();       //删除第 i 个元素,并把值放到 e 中ListLength();       //获得 L 的长度endADT

一般抽象数据类型中定义的是关于这个数学模型最基本的操作,其他复杂的操作都可以由这些基本操作组合而成。

线性表中的顺序存储结构

这里指的是线性表的物理结构,也就是在内存中的存储结构。顺序结构就是在内存中划出一个指定大小的内存块,用来存储数据。让这些数据不只是在逻辑上相邻,在物理存储上也是相邻的。

那实现顺序存储结构的代码怎么写呢?下面就是啦,当然是用最爱的 Java 写的啦。

public class ArrayList1<E> {    private static final int defaultSize = 20;    private int length;//当前长度    private int size;//线性表大小    private Object data[];    public ArrayList1(){        InitList(defaultSize);    }    public ArrayList1(int size){        InitList(size);    }    //初始化线性表    private void InitList(int size){        this.size = size;        data = new Object[size];    }    //判断表示否为空    protected boolean ListIsEmpty(){        if(this.length == 0){            return true;        }        return false;    }    /*清空线性表,表面上是把所有数据清空,其实是让程序认定这没有数据。     也就是把 length 置为零*/    protected void ClearList(){        this.length = 0;    }    //返回顺序表中第 i 个位置的元素,    //时间复杂度为 O(1)    protected E GetElem(int i){        if(i <= 0 || i > this.length){            return  null;        }        return (E) this.data[i-1];    }    //在第 i 个位置插入元素 e,没写好    //时间复杂度为 O(n)    protected boolean ListInsert(int i,E e){        if(i <= 0 || i > this.size){            return false;        }else if(i == this.length + 1){                this.data[i-1] = e;                this.length++;            return true;        }else if(i > this.length + 1 && i <= this.size){                this.data[this.length] = e;                this.length++;            return true;        }        for(int j = (this.length - i + 1);j > 0;j--){            this.data[j + 1] = this.data[j];        }        this.data[i-1] = e;        this.length++;        return true;    }    //在第 i 个位置删除元素    //时间复杂度为 O(n)    protected boolean ListDelete(int i){        if(i <= 0 || i > this.length){            return false;        }        for(int j = i;j < this.length;j++){            this.data[j-1] = this.data[j];        }        this.length--;        return true;    }    //获得线性表长度    protected int ListLength(){        return this.length;    }    //获得线性表长度    protected int ListSize(){        return this.size;    }    //判断是否有和指定元素相同的,有,则返回它所在位置序号,没有则返回 -1     protected int LocateElem(E e){        for(int i = 0;i < this.length;i++){            if(e.equals(this.data[i])){                return i+1;            }        }        return -1;    }}

通过时间复杂度的推导,可以知道顺序表中插入和删除的时间复杂度为 O(n),查询的时间复杂度为 O(1)。

由此可得出,顺序表优点是查询快,缺点是添加、删除慢。

线性表中的链式存储结构

由于顺序存储结构在插入和删除时,时间复杂度过大,然后科学家们就想出了链式存储的方式来解决这个问题。

链式存储不强制采用一块地址连续的空间来存储数据,可以连续,也可以不连续,只要保证他在逻辑上是相邻的就可以了。

实现的方式是每一个通过结点来存储数据,以及下一个结点所在的地址。如下图所示,

这里写图片描述

具体实现代码如下:(这里讨论的是有头结点的单链表)

public class LinkedList1<E>{        private class Node{//结点            private E e;//数据域            private Node next;//指针域            public Node(E e){                this.e = e;                this.next = null;            }            public Node() {            }            public E getE() {                return e;            }            public void setE(E e) {                this.e = e;            }            public Node getNext() {                return next;            }            public void setNext(Node next) {                this.next = next;            }        }        private Node head;//头结点        private int size;//链表大小        public LinkedList1(){            head = new Node();            this.size = 0;        }        //添加到后面        protected boolean add(E e){            Node n = new Node(e);            if(this.size == 0){                head.setNext(n);                }else{                Node node = get(this.size);                node.setNext(n);            }            this.size++;            return true;        }        //返回指定位置的结点        protected E getElem(int position){            if(this.size == 0 || position <= 0 || position > this.size){                return null;            }            Node node = get(position);            return node.getE();        }        //判断是否为空        protected boolean ListIsEmpty(){            if(this.size == 0){                return true;            }else{                return false;            }        }        //得到大小        protected int ListLength(){            return this.size;        }        //清空        protected void ClearList(){            this.head.setNext(null);            this.size = 0;        }        //删除某个位置的元素        // head a b c d e 5 /2        protected boolean ListDelete(int p){            if(p <= 0 || p > this.size){                return false;            }            Node node = get(p);            Node nodeBefore = get(p-1);            nodeBefore.setNext(node.getNext());            this.size--;            return true;        }        //在某个位置上插入元素        //head a b c d e 5 3        protected boolean ListInsert(E e,int p){            if(p <= 0 || p > this.size + 1){                return false;            }            if(p == this.size + 1){                add(e);                return true;            }            Node newNode = new Node(e);            Node node = get(p-1);            newNode.setNext(node.getNext());            node.setNext(newNode);            this.size++;            return true;        }         //这个方法的时间复杂度还是 O(n)        private Node get(int p){            if(p == 0){                return head;            }            Node node = head;            for(int i = 0;i<p;i++){                node = node.getNext();            }            return node;        }

链表还有几种其他变形,静态链表,循环链表,双向链表等,其实本事上都差不多。

静态链表,就是利用一个数组来存储结点,结点包含了数据域和指针域。

循环链表,就是尾结点的指针域不再为空,而是指向头结点,构成循环。

双向链表,就是一个结点包含一个数据域和两个指针域,分别用来指向后一个结点和前一个节点。

总之,实际开发中,利用它们之间的特性,选择合适的数据结构完成上一层次的需求,就可以了。

补充,上述对于链表的实现其实写的不对,上面 get() 方法的时间复杂度是O(n),而插入和删除都使用了这个方法,导致时间复杂度也是 O(n) ,而不是链表本来的 O(1) ,这里的确写错了。

正确的写法应该是加上一个尾指针,专门用来指向末尾结点,至少可以保证直接在末尾添加时时间复杂度是 O(1)

原创粉丝点击