数据结构之线性表

数据结构之线性表

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

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

ADT 线性表 (List)
Data
    线性表的数据对象集合用数学方法可以这样表示{a1,a2a3,...an,},其中除了第一个元素没有前驱元素,最后一个元素没有后驱元素,内一个元素都是有且只有一个前驱元素和后驱元素。数据元素之间的关系是一对一的关系。(其实这么一大段话就是用文字把线性表这个概念描述出来而已,心里只要想着排队就可以了)

Operation

InitList();         //初始化操作,建立一个新的线性表 L
ListIsEmpty();      //判断线性表是否为空
ClearList();        //清空线性表
GetElem();          //获得 L 中第 i 个元素的值,并返回给 e 
LocateElem();       //判断 L 中是否有和 e 相同的元素,有就返回1,没有返回0
ListInsert();       //在第 i 个位置插入元素 e
ListDelete();       //删除第 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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值