JAVA数据结构和算法:第二章(表)

线性表的定义

线性表,从名字上来看,像线连起来的表。每个元素都是连起来的,比如在体育课按照老师定好的队列排队的时候,有一个打头,一个收尾,中间的每个人都知道前面是谁,后面是谁,就像一根线将他们联系在一起,就可以称之为线性表。

这时候我们来看几个关键点,首先元素之间是有顺序的,并且第一个元素无前驱,最后一个元素无后继,其他的元素都有且只有一个前驱和后继,这样才能形成线性表。

我们用数学语言来进行定义,若有线性表(a1,a2,….,ai-1,ai,ai+1,…,an),则称表中ai-1是ai的直接前驱元素,ai+1是ai的直接后继元素,a1没有直接前驱元素,an没有直接后继元素。线性表元素的个数定义为线性表的长度,当n=0时的特殊表我们称之为空表。

我们来举几个例子,每年的星座列表是不是线性表?当然是,因为星座以白羊打头,双鱼收尾,其中的都有且只有一个前驱和后继,所以完全符合线性表的条件。 
那么公司的上下级关系是不是线性表?当然不是,因为一个上级会有很多个下级,每个人不只有一个后继和前驱,不符合线性表的条件。

前面我们明确定义了线性表的概念,与这些概念相关的是线性表应该具有的操作集合。

例如,向表中插入一个数据,或者获取表中的某个元素,删除某个元素,获取某个元素的直接前驱后继等操作,当然对于不同的应用,线性表应该有什么操作都是不同的,完全由程序设计者来决定,当然像增删查等必要的操作基本是每个线性表都需要具有的。

顺序存储结构

线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。

这里写图片描述

说白了,就是在内存中找一块位置,把一定的内存空间占了,然后把相同数据类型的数据元素按顺序存放在这儿。说到这儿,我们可能有人会想起来,和数组怎么那么像呢?对,线性表的每个数据元素类型都相同,所以我们完全可以通过数组来实现线性表的顺序存储结构

如果我们用数组来实现的话,我们就必须需要两个属性:线性表的长度,需要的数组长度。并且要保证线性表的长度要小于数组的长度,因为我们可能还需要执行插入操作。

java顺序结构线性表的实现

扫描二维码关注公众号,回复: 1878720 查看本文章
public class LinearList<T>  {
    /**
     * 对象数组,用数组来存放线性表的数据
     */
    protected Object[] element;

    /**
     * 线性表长度,记载元素个数
     */
    protected int len;

    /**
     * 默认构造方法,创建默认容量的空表
     */
    public LinearList() {
        this(64);
    }

    /**
     * 构造方法,创建容量为size的空表
     * 
     * @param size
     */
    public LinearList(int size) {
        this.element = new Object[size];
        this.len = 0;
    }

    /**
     * 判断顺序表是否空,若空返回true,O(1)
     */
    public boolean isEmpty() {
        return this.len == 0;
    }

    /**
     * 返回顺序表长度,O(1)
     */
    public int length() {
        return this.len;
    }

    /**
     * 返回第i(≥0)个元素。若i<0或大于表长则返回null,O(1)
     */

    public T get(int i) {
        if (i >= 0 && i < this.len) {
            return (T) this.element[i];
        }
        return null;
    }

    /**
     * 设置第i(≥0)个元素值为x。若i<0或大于表长则抛出序号越界异常;若x==null,不操作
     */
    public void set(int i, T x) {
        if (x == null)
            return;

        if (i >= 0 && i < this.len) {
            this.element[i] = x;
        } else {
            throw new IndexOutOfBoundsException(i + ""); // 抛出序号越界异常
        }
    }

    /**
     * 顺序表的插入操作 插入第i(≥0)个元素值为x。若x==null,不插入。 若i<0,插入x作为第0个元素;若i大于表长,插入x作为最后一个元素 
     * 思路:从最后一个元素往前遍历到第i个位置,分别将他们都往后移一位
     */
    public void insert(int i, T x) {
        if (x == null)
            return;

        // 若数组满了,则扩充数组容量
        if (this.len == element.length) {
            // temp也引用elements数组
            Object[] temp = this.element;
            // 重新申请一个双倍的数组
            this.element = new Object[temp.length * 2];
            // 复制数组元素,O(n)
            for (int j = 0; j < temp.length; j++) {
                this.element[j] = temp[j];
            }
        }

        // 下标容错
        if (i < 0)
            i = 0;

        if (i > this.len)
            i = this.len;

        // 元素后移,平均移动len/2
        for (int j = this.len - 1; j >= i; j--) {
            this.element[j + 1] = this.element[j];
        }
        this.element[i] = x;
        this.len++;
    }

    /**
     * 在顺序表最后插入x元素
     */
    public void append(T x) {
        insert(this.len, x);
    }

    /**
     * 顺序表的删除操作 删除第i(≥0)个元素,返回被删除对象。若i<0或i大于表长,不删除,返回null。
     * 思路:从删除元素的位置到最后遍历,每一个元素向前移一位
     */

    public T remove(int i) {
        if (this.len == 0 || i < 0 || i >= this.len) {
            return null;
        }

        T old = (T) this.element[i];
        // 元素前移,平均移动len/2
        for (int j = i; j < this.len - 1; j++) {
            this.element[j] = this.element[j + 1];
        }
        this.element[this.len - 1] = null;
        this.len--;
        return old;
    }

    /**
     * 删除线性表所有元素
     */
    public void removeAll() {
        this.len = 0;
    }

    /**
     * 查找,返回首次出现的关键字为key元素
     */
    @SuppressWarnings("unchecked")
    public T search(T key) {
        int find = this.indexOf(key);
        return find == -1 ? null : (T) this.element[find];
    }

    /**
     * 顺序表比较相等 比较两个顺序表是否相等 ,覆盖Object类的equals(obj)方法,O(n)
     */

    public boolean equals(Object obj) {
        if (this == obj)
            return true;

        if (obj instanceof LinearList) {
            LinearList<T> list = (LinearList<T>) obj;
            // 比较实际长度的元素,而非数组容量
            for (int i = 0; i < this.length(); i++) {
                if (!this.get(i).equals(list.get(i))) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }


    // 返回顺序表所有元素的描述字符串,形式为“(,)”,覆盖Object类的toString()方法
    public String toString() {
        String str = "(";
        if (this.len > 0)
            str += this.element[0].toString();
        for (int i = 1; i < this.len; i++)
            str += ", " + this.element[i].toString();
        return str + ") "; // 空表返回()
    }
}

    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178

上面是我们实现的顺序结构存储的线性表,我们已经发现了插入和删除是最麻烦的,步骤最多,需要很多遍历,我们来分析一下这两个操作的时间复杂度。 
如果要插入的或者删除的是最后一个元素,那么不需要移动任何元素,这时候为O(1). 
如果要插入的或者删除的是第一个元素,那么所有元素都需要移动,这时候为O(n). 
而当执行存储和查询的时候,时间复杂度都为O(1),这也间接的说明了一个问题,它比较适合元素个数不变化的存取操作,而插入和删除很不方便

线性表顺序结构存储的优缺点

  • 优点:可以快速的存取表中任意位置的数据
  • 缺点:插入和删除需要移动元素,花费大量时间,并且当数组扩容时,难以确定存储空间的容量,容易造成存储空间的碎片化。-

链式存储结构

前面我们已经证明了,线性表的顺序存储结构有一个很大的缺点,就是插入和删除操作时需要消耗大量时间,能不能想个办法解决一下呢?

首先我们需要思考一下为什么会造成这个情况,仔细思考其实是因为两个相邻元素之间在内存中也是紧挨着的,如果要插入自然要向后移,否则会出现覆盖,要删除,后面的元素自然也要向前移。那么我们应该怎么解决这一情况呢?我们可不可以让逻辑上相邻的两个元素,在内存中其实不是相邻的,而只需前一个元素知道后一个元素的地址,然后连起来就行了?对,这就是我们要讲的链式存储结构。

这里写图片描述

通常讲采用链式存储结构的线性表称为线性链表,从链接方式来看,链表可分为单链表、循环链表和双链表。从实现角度来看,链表分为动态链表和静态链表。

链表用一组任意的存储单元来存储线性表的元素,这组存储单元可以连续也可以不连续,所以现在每个元素中除了要存储数据,还要存储其后继元素的存储地址。我们把存储数据元素信息的域称为数据域,把存储后继元素位置的域称为指针域,这两部分共同组成一个数据元素单元,我们把它叫做结点(Node)。

单链表

每个结点中的指针域只包含下一个结点的位置,叫做单链表。单链表的最后一个结点指向为null。

这里写图片描述

单链表的java实现


首先,我们要实现一个结点类Node 

public class Node<T> {
    /**
     * 数据域,保存数据元素
     */
    public T data;
    /**
     * 地址域,引用后继结点
     */
    public Node<T> next;
    /**
     * 构造节点
     */
    public Node() {
        this(null, null);
    }

    /**
     * 构造结点,data指定数据元素,next指定后继结点
     * 
     * @param data
     * @param next
     */
    public Node(T data, Node<T> next) {
        this.data = data;
        this.next = next;
    }

    /**
     * 返回结点元素值对应的字符串
     */

    public String toString() {
        return this.data.toString();
    }

}
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
public class SingleLinkedList<T> {
        /**
         * 指定头结点
         */
        public Node<T> head;

        /**
         * 默认构造方法,构造空单链表
         */
        public SingleLinkedList() {
            // 创建头结点,data和next值均为null
            this.head = new Node<T>();
        }

        /**
         * 由指定数组中的多个对象构造单链表。采用尾插入构造单链表
         * 头插入就是每次新的结点在前面,例如第一个结点在头结点的前面,以此类推
         * 头插入不太符合我们逻辑,所以尾插入,生成的结点在后面
         * 
         * 若element==null,Java将抛出空对象异常;若element.length==0,构造空链表
         * 思路:让一个结点指向最后一个结点,刚开始就一个头结点,然后循环遍历数组,将最后一个结点的地址域设置为数组的元素
         * 
         * @param element
         */
        public SingleLinkedList(T[] element) {
            // 创建空单链表,只有头结点
            this();
            // last指向单链表最后一个结点
            Node<T> last = this.head;
            for (int i = 0; i < element.length; i++) {
                last.next = new Node<T>(element[i], null);
                last = last.next;
            }
        }

        /**
         * 判断单链表是否空,O(1)
         * 判断头节点的地址域是否为空即可
         */

        public boolean isEmpty() {
            return this.head.next == null;
        }

        /**
         * 返回单链表长度,O(n), 基于单链表遍历算法
         * 数组中长度定义时就确定,单链表就必须得从头开始向后计数
         */

        public int length() {
            //定义i用来计数
            int i = 0;
            // p从单链表第一个结点开始
            Node<T> p = this.head.next;
            // 若单链表未结束
            while (p != null) {
                i++;
                // p到达后继结点
                p = p.next;
            }
            return i;
        }

        /**
         * 获得第i(≥0)个元素,若i<0或大于表长则返回null,O(n)
         */

        public T get(int i) {
            if (i >= 0) {
                //定义一个头结点,从头开始查找
                Node<T> p = this.head.next;
                //如果j在跳出循环时不为空,说明i处元素存在,否则说明元素不存在
                for (int j = 0; p != null && j < i; j++) {
                    p = p.next;
                }

                // p指向第i个结点
                if (p != null) {
                    return p.data;
                }
            }
            return null;
        }

        /**
         * 设置第i(≥0)个元素值为x。若i<0或大于表长则抛出序号越界异常;若x==null,不操作。O(n)
         */

        public void set(int i, T x) {
            if (x == null)
                return;
            //还是从头结点开始
            Node<T> p = this.head.next;
            for (int j = 0; p != null && j < i; j++) {
                p = p.next;
            }

            //如果插入的位置>0,并且循环后的p不为null,则赋值给这个结点
            if (i >= 0 && p != null) {
                p.data = x;
            } else {
                throw new IndexOutOfBoundsException(i + "");
            }
        }

        /**
         * 插入第i(≥0)个元素值为x。若x==null,不插入。 若i<0,插入x作为第0个元素;若i大于表长,插入x作为最后一个元素。O(n)
         * 初始化从第一个结点开始,当j<i时遍历节点,若到末尾结点为空,说明第i个元素不存在
         * 如果存在,就创建一个新结点,然后相互赋值即可
         */

        public void insert(int i, T x) {
            // 不能插入空对象
            if (x == null) {
                return;
            }

            // p指向头结点
            Node<T> p = this.head;
            // 寻找插入位置
            for (int j = 0; p.next != null && j < i; j++) {
                // 循环停止时,p指向第i-1结点或最后一个结点
                p = p.next;
            }
            // 插入x作为p结点的后继结点,如果i<0,则p还是为头结点,所以是在最前面插入
            //如果i>链表长度,则p指向最后一个元素,所以是插入到最后
            p.next = new Node<T>(x, p.next);
        }

        /**
         * 在单链表最后添加x对象,O(n)
         * 直接定义一个比较大的数,然后插入的时候会遍历,当结点为空时说明是最后,然后跳出循环
         * 直接插入到最后
         */

        public void append(T x) {
            insert(Integer.MAX_VALUE, x);
        }

        /**
         * 删除第i(≥0)个元素,返回被删除对象。若i<0或i大于表长,不删除,返回null。O(n)
         * 思路:从头结点开始遍历,如果遍历结束,结点为空,则说明第i个元素不存在,否则就查找成功
         * 然后将删除元素的地址域赋值给前一个元素即可,然后返回删除元素的数据
         */

        public T remove(int i) {
            if (i >= 0) {
                Node<T> p = this.head;
                for (int j = 0; p.next != null && j < i; j++) {
                    //因为我们是从链表元素是从0开始,所以p并不是我们要删除的元素,p的下一个才是
                    p = p.next;
                }

                if (p != null) {
                    // 获得原对象
                    T old = p.next.data;
                    // 删除p的后继结点
                    p.next = p.next.next;
                    return old;
                }
            }
            return null;
        }

        /**
         * 删除单链表所有元素 Java将自动收回各结点所占用的内存空间 
         * 我们只需要将头结点设为null,其他的jvm会帮我们回收垃圾
         */

        public void removeAll() {
            this.head.next = null;
        }

        /**
         * 顺序查找关键字为key元素,返回首次出现的元素,若查找不成功返回null
         * key可以只包含关键字数据项,由T类的equals()方法提供比较对象相等的依据
         */

        public T search(T key) {
            if (key == null)
                return null;
            for (Node<T> p = this.head.next; p != null; p = p.next){
                    if (p.data.equals(key))
                        return p.data;
                   } 
            return null;
        }

        /**
         * 返回单链表所有元素的描述字符串,形式为“(,)”,覆盖Object类的toString()方法,O(n)
         */

        public String toString() {
            String str = "(";
            for (Node<T> p = this.head.next; p != null; p = p.next) {
                str += p.data.toString();
                if (p.next != null)
                    str += ","; // 不是最后一个结点时后加分隔符
            }
            return str + ")"; // 空表返回()
        }

        /**
         * 比较两条单链表是否相等
         */

        public boolean equals(Object obj) {
            if (obj == this)
                return true;

            if (obj instanceof SingleLinkedList) {
                SingleLinkedList<T> list = (SingleLinkedList<T>) obj;
                return equals(this.head.next, list.head.next);
            }
            return false;
        }

        /**
         * 比较两条单链表是否相等,递归方法
         * 
         * @param p
         * @param q
         * @return
         */
        private boolean equals(Node<T> p, Node<T> q) {
            return p == null && q == null || p != null && q != null
                    && p.data.equals(q.data) && equals(p.next, q.next);
        }
}
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229

单链表的优缺点

通过代码的编写,我们发现单链表的查找方法时间复杂度为O(n),而插入和删除为O(1)

  • 优点:执行插入和删除效率高,不需要事先分配空间,元素个数不受限制,不会浪费空间

  • 缺点:查找效率比较低

我们可以得出一些结论,若线性表需要频繁查找,很少进行插入和删除操作时,则使用顺序存储结构。反之,需要大量的插入和删除时,则使用链式存储结构。

循环链表

循环链表是一个首尾相连的链表,将单链表最后一个结点的地址域改为指向表头结点,就得到了单链循环链表,称为循环单链表。

循环单链表和单链表的主要差异就在循环的判断条件上,单链表判断循环结束是看地址域是否为空,而循环单链表则是判断地址域是否是头结点。在前面的单链表中,我们总是用一头结点来指向我们的第一个数据元素,这样非常方便。在单链表中,我们有了头结点,可以很快的访问到第一个元素,访问最后一个元素却需要O(n),循环链表就可以解决这个问题,使访问第一个元素和最后一个元素都是O(1),怎么实现呢?再添加一个指向尾部的结点。

循环单链表的实现


public class CircleSingleLinkedList<T> {
        /**
         * 头指针,指向循环单链表的头结点
         * 尾指针,指向循环单链表的最后一个结点
         */
        public Node<T> head;
        public Node<T> tail;

        /**
         * 默认构造方法,构造空循环单链表
         * 即让自己指向自己
         * 然后让尾指针指向最后一个结点,此时也是指向头结点
         */
        public CircleSingleLinkedList() { 
            this.head = new Node<T>();
            this.head.next = this.head;
            this.tail=new Node<T>();
            this.tail.next=this.head;
        }

        /**
         * 判断循环单链表是否空
         * 判断头结点的地址域是不是自己即可
         */

        public boolean isEmpty() {
            return this.head.next == this.head;
        }

        /**
         * 返回循环单链表长度,单链表遍历算法,O(n)
         * 注意我们说的判断条件的区别
         */

        public int length() {
            int i = 0;
            for (Node<T> p = this.head.next; p != this.head; p = p.next) {
                i++;
            }
            return i;
        }

        /**
         * 返回第i(≥0)个元素,若i<0或大于表长则返回null,O(n)
         */

        public T get(int i) {
            if (i >= 0) {
                Node<T> p = this.head.next;
                for (int j = 0; p != this.head && j < i; j++)
                    p = p.next;
                if (p != this.head)
                    return p.data; // p指向第i个结点
            }
            return null;
        }

        /**
         * 设置第i(≥0)个元素值为x。若i<0或大于表长则抛出序号越界异常;若x==null,不操作。O(n)
         */

        public void set(int i, T x) {
            // 不能设置空对象
            if (x == null)
                return;
            Node<T> p = this.head.next;
            for (int j = 0; p != this.head && j < i; j++)
                p = p.next;
            // p指向第i个结点
            if (i >= 0 && p != this.head)
                p.data = x;
            else
                throw new IndexOutOfBoundsException(i + ""); // 抛出序号越界异常
        }


        public void insert(int i, T x) {
            // 不能插入空对象
            if (x == null) 
                return;

            // p指向头结点
            Node<T> p = this.head;
            // 寻找插入位置
            for (int j = 0; p.next != this.head && j < i; j++) {
                // 循环停止时,p指向第i-1结点或最后一个结点
                p = p.next;
            }
            // 插入x作为p结点的后继结点,包括头插入(i<=0)、中间/尾插入(i>0) 
            p.next = new Node<T>(x, p.next);
            //每插入一个都要保证尾结点指向最后一个元素
            this.tail.next=p.next;
        }


        public void append(T x) {
            insert(Integer.MAX_VALUE, x);
        }

        /**
         * 删除第i(≥0)个元素,返回被删除对象。若i<0或i大于表长,不删除,返回null。O(n)
         */

        public T remove(int i) {
            if (i >= 0) {
                Node<T> p = this.head;
                for (int j = 0; p.next != this.head && j < i; j++) {
                    p = p.next;
                }

                if (p != null) {
                    // 获得原对象
                    T old = p.next.data;
                    // 删除p的后继结点
                    p.next = p.next.next;
                    return old;
                }
            }
            return null;
        }

        /**
         * 删除单链表所有元素 Java将自动收回各结点所占用的内存空间
         */

        public void removeAll() {
            this.head.next = this.head;
        }

        /**
         * 顺序查找关键字为key元素,返回首次出现的元素,若查找不成功返回null
         * key可以只包含关键字数据项,由T类的equals()方法提供比较对象相等的依据
         */

        public T search(T key) {
            if (key == null)
                return null;
            for (Node<T> p = this.head.next; p != this.head; p = p.next)
                if (p.data.equals(key))
                    return p.data;
            return null;
        }

        /**
         * 返回循环单链表所有元素的描述字符串
         */

        public String toString() {
            String str = "(";
            Node<T> p = this.head.next;
            // 遍历单链表的循环条件改变了
            while (p != this.head) {
                str += p.data.toString();
                if (p != this.head)
                    str += ", "; // 不是最后一个结点时后加分隔符
                p = p.next;
            }
            return str + ")";
        }

        /**
         * 比较两条单链表是否相等
         */
        @SuppressWarnings("unchecked")

        public boolean equals(Object obj) {
            if (obj == this)
                return true;

            if (obj instanceof CircleSingleLinkedList) {
                CircleSingleLinkedList<T> list = (CircleSingleLinkedList<T>) obj;
                return equals(this.head.next, list.head.next);
            }
            return false;
        }

        /**
         * 比较两条单链表是否相等,递归方法
         * 
         * @param p
         * @param q
         * @return
         */
        private boolean equals(Node<T> p, Node<T> q) {
            return p == null && q == null || p != null && q != null
                    && p.data.equals(q.data) && equals(p.next, q.next);
        }  

        //合并两条循环单链表 
        /*有了尾结点这个工作将变得无比轻松,我们只需要让A链表的尾部指向B链表的第一个结点(不是头结点)
        然后让B链表的尾结点指向A链表的头结点即可
        */
        public void merge(CircleSingleLinkedList t) {
             Node <T> n=this.tail.next.next;
             this.tail.next.next=t.tail.next.next.next;
             t.tail.next.next=n;
         }
}
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199

双向链表

我们在单链表中如果要查找上一个结点的话,那就得从头开始循环了,最坏的时间复杂度就是O(n), 
你会想这也太麻烦了,我能不能像next直接查找下一个结点一样,设计链表可以直接查找上一个结点呢?当然可以,为了克服上述问题,前辈们设计了双向链表。

双向链表:就是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。所以在双向链表中的每个结点都有两个指针域,一个指向直接后继,一个指向直接前驱。

既然单链表可以有循环链表,那么双向链表当然也可以循环。

双向循环链表为空时

这里写图片描述

双向循环链表不为空时

这里写图片描述

双向循环链表的实现

//先创建一个指向前驱和后继结点的结点对象

public class DLinkNode<T> {
     public T data;

        /**
         * pred指向前驱结点,next指向后继结点
         */
        public DLinkNode<T> pred;
        public DLinkNode<T> next;

        /**
         * 构造节点,data指定元素,pred指向前驱节点,next指向后继节点
         */
        public DLinkNode(T data, DLinkNode<T> pred, DLinkNode<T> next) {
            this.data = data;
            this.pred = pred;
            this.next = next;
        }
        /**
         * 默认构造器
         */
        public DLinkNode() {
            this(null, null, null);
        }
}
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
//实现双向循环链表

public class CircleDoubleLinkedList<T> { 
    //头结点
    public DLinkNode<T> head;

    //默认构造方法
    public CircleDoubleLinkedList() {
        this.head=new DLinkNode<T>();
        this.head.pred=this.head;
        this.head.next=this.head;
    } 

    //将数组构造成循环双链表
    public CircleDoubleLinkedList(T[] element) {
        this(); 
        //rear指向最后的结点
        DLinkNode<T> rear=this.head;
        for(int i=0;i<element.length;i++) {
            rear.next=new  DLinkNode<T>(element[i],rear,this.head);
            rear=rear.next;
        } 
        //将头结点的直接前驱结点设置为最后一个结点
        this.head.pred=rear;
    }


        public boolean isEmpty() {
            return this.head.next == this.head;
        }

        /**
         * 获取链表的长度
         */
        public int length() {
            int i = 0;
            for (DLinkNode<T> p = this.head.next; p != this.head; p = p.next) {
                i++;
            }
            return i;
        }

        /**
         * 返回第i(≥0)个元素,若i<0或大于表长则返回null,
         */

        public T get(int i) {
            if (i >= 0) {
                DLinkNode<T> p = this.head.next;
                for (int j = 0; p != this.head && j < i; j++) {
                    p = p.next;
                }

                // p指向第i个结点
                if (p != null) {
                    return p.data;
                }
            }
            return null;
        }

        /**
         * 设置第i(≥0)个元素值为x。若i<0或大于表长则抛出序号越界异常;若x==null,不操作。O(n)
         */

        public void set(int i, T x) {
            if (x == null)
                return;

            DLinkNode<T> p = this.head.next;
            for (int j = 0; p != this.head && j < i; j++) {
                p = p.next;
            }

            // p指向第i个结点
            if (i >= 0 && p != null) {
                p.data = x;
            } else {
                throw new IndexOutOfBoundsException(i + "");
            }
        }

        /**
         * 插入第i(≥0)个元素值为x。若x==null,不插入。 若i<0,插入x作为第0个元素;若i大于表长,插入x作为最后一个元素。O(n)
         */

        public void insert(int i, T x) {
            if (x == null)
                return;
            DLinkNode<T> p = this.head;
            // 寻找插入位置 循环停止时,p指向第i-1个结点
            for (int j = 0; p.next != this.head && j < i; j++) {
                p = p.next;
            }
            // 插入在p结点之后,包括头插入、中间插入
            DLinkNode<T> q = new DLinkNode<T>(x, p, p.next);
            p.next.pred = q;
            p.next = q;
        }

        /**
         * 在循环双链表最后添加结点,O(1)
         */
        public void append(T x) {
            if (x == null)
                return;
            // 插入在头结点之前,相当于尾插入
            DLinkNode<T> q = new DLinkNode<T>(x, head.pred, head);
            head.pred.next = q;
            head.pred = q;
        }

        /**
         * 删除第i(≥0)个元素,返回被删除对象。若i<0或i大于表长,不删除,返回null。O(n)
         */

        public T remove(int i) {
            if (i >= 0) {
                DLinkNode<T> p = this.head.next;
                // 定位到待删除结点
                for (int j = 0; p != this.head && j < i; j++) {
                    p = p.next;
                }

                if (p != head) {
                    // 获得原对象
                    T old = p.data;
                    // 删除p结点自己
                    p.pred.next = p.next;
                    p.next.pred = p.pred;
                    return old;
                }
            }
            return null;
        }

        /**
         * 删除循环双链表所有元素
         */

        public void removeAll() {
            this.head.pred = this.head;
            this.head.next = this.head;
        }

        /**
         * 顺序查找关键字为key元素,返回首次出现的元素,若查找不成功返回null
         * key可以只包含关键字数据项,由T类的equals()方法提供比较对象相等的依据
         */

        public T search(T key) {
            if (key == null)
                return null;
            for (DLinkNode<T> p = this.head.next; p != this.head; p = p.next)
                if (key.equals(p.data))
                    return p.data;
            return null;
        }

        /**
         * 返回循环双链表所有元素的描述字符串,循环双链表遍历算法,O(n)
         */

        public String toString() {
            String str = "(";
            for (DLinkNode<T> p = this.head.next; p != this.head; p = p.next) {
                str += p.data.toString();
                if (p.next != this.head)
                    str += ",";
            }
            return str + ")"; // 空表返回()
        }

        /**
         * 比较两条循环双链表是否相等,覆盖Object类的equals(obj)方法
         */
        @SuppressWarnings("unchecked")
        public boolean equals(Object obj) {
            if (obj == this)
                return true;
            if (!(obj instanceof CircleDoubleLinkedList))
                return false;
            DLinkNode<T> p = this.head.next;
            CircleDoubleLinkedList<T> list = (CircleDoubleLinkedList<T>) obj;
            DLinkNode<T> q = list.head.next;
            while (p != head && q != list.head && p.data.equals(q.data)) {
                p = p.next;
                q = q.next;
            }
            return p == head && q == list.head;
        }
}    

    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193

双向循环链表就是典型的用空间换时间。

JAVA API中的表实现

JAVA的类库中,包含着很多数据结构的实现,设计的很精妙,所以说JDK源码就是我们非常好的学习工具。Collection接口中有很多使用我们数据结构来实现的集合类,我们今天就来看看集合中的那些表实现。

Collecation接口中定义了很多集合类中应该有的方法,并且实现了Iterable接口,实现Iterable接口的类可以使用增强for循环。

实现Iterable接口的集合必须提供Iterator方法,该方法返回一个Iterator对象,Iterator也是java.util包中的一个接口,Iterator接口主要是为了遍历集合,将当前的位置在对象内部存储起来,然后就可以进行遍历。当一个增强for循环在循环一个实现Iterable接口的对象时,其实增强for循环内部就是使用的Iterator对象在进行遍历。

好了,说了这么多,我们扯得有点远,我们这次最大的目的是来看看jdk中的表实现,它由java.util包中的List接口指定,它下面有很多实现类,例如ArrayList类就是顺序结构线性表的实现,存取指定位置元素比较方便,而插入和删除效率就很低。LinkedList类则是双链表的实现,存取指定位置元素效率比较低,而插入和删除的效率则高于ArrayList。

尽管前面我们已经知道了顺序结构线性表和链表的差别,这里我们再来看一下ArrayList和LinkedList的差别。

  • 不论是对于ArrayList还是LinkedList而言,在后端添加N个元素的操作运行时间都是O(n).

  • 如果从集合前面添加N个元素的话,,LinkedList的运行时间是O(n),而ArrayList每添加一个就是O(n),添加n个则是O(n^2)

  • 对于通过get()计算集合中n个数的和的方法,ArrayList的运行时间是O(n),LinkedList则为O(n^2),但是如果使用迭代器的话,那么任意的List集合运行时间都是O(n),因为迭代器会记录当前 
    位置。

小例子:remove方法对LinkedList的使用

我们来做一个例子,将一个表中的所有偶数都删除,我们当然不可能使用ArrayList,因为每次操作都是O(n),所以我们要使用LinkedList..

我们先来一种我们正常思考的算法。

    public void removeEven(LinkedList<Integer> lst) {
        int i=0;
        while(i<lst.size()) {
            if(lst.get(i)%2==0) {
                lst.remove(i);
            }
            i++;
        }
     }

上面是我们最经常想到的方法,但是也暴露出了两个问题,首先LinkedList的get的效率是很低的,为O(n),并且remove方法效率也很低,也是O(n)。所以整个方法的时间为O(n^2) 
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

怎么解决这两个问题呢?我们用我们前面提到的迭代器。

    public void removeEven(LinkedList<Integer> lst) {
        Iterator<Integer> i=lst.iterator();
        while(i.hasNext()) {
            if(i.next()%2==0) {
                i.remove();
            }       
        }
    }  
    使用迭代器的话,删除只需要花费常数时间,因为迭代器就在删除元素这里,所以整个函数只需要O(n)的时间,效率大大提高。


    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

ArrayList的实现

我们前面实现的顺序结构存储的线性表其实就是ArrayList的内部实现,如果有什么模糊的地方可以去前面看看源代码,这里我们来实现点更接近ArrayList的功能,那就是迭代器。

我们先来编写一个迭代器

public class LinearListIterator<T> implements Iterator<T> {

    private int current=0;
    private LinearList<T> linearList;


    public LinearListIterator(LinearList<T> linearList) {
        this.linearList=linearList;
    } 
    //判断当前项有没有大于表的长度
    @Override
    public boolean hasNext() {
        return current<linearList.len;
    }
    //返回当前项,并加1记录当前位置
    @Override
    public T next() {
        return (T) linearList.element[current++];
    } 


}


    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
我们自己编写的ArrayList必须实现Iterable接口,然后实现他的方法,返回一个迭代器对象

@Override
public Iterator<T> iterator() {
    return new LinearListIterator(this);
}
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
我们测试一下我们的迭代器 
    public static void main(String[] args) {

        LinearList<Integer> l=new LinearList<Integer>();
        l.append(1);
        l.append(2);
        l.append(3);
        l.append(4);
        l.append(5);
        LinearListIterator<Integer> iterator=(LinearListIterator<Integer>) l.iterator(); 
        //可以正常输出
        while(iterator.hasNext()) {
            System.out.println(iterator.next());
        }   
    } 
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

当然ArrayList内部采用的是内部类来实现迭代器,这样有一个很明显的优点就是可以直接操作当前的集合对象,而不用别人再来传递,并且可以获得当前集合的所有属性来操作。

LinkedList的实现 
JDK中的LinkedList是一个双向链表,并且没有循环。我们前面也实现了,具体可以看看前面的代码。 
至于LinkedList迭代器的实现,我想大家有了前面的基础,会很容易的实现。

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/c99463904/article/details/77429699

线性表的定义

线性表,从名字上来看,像线连起来的表。每个元素都是连起来的,比如在体育课按照老师定好的队列排队的时候,有一个打头,一个收尾,中间的每个人都知道前面是谁,后面是谁,就像一根线将他们联系在一起,就可以称之为线性表。

这时候我们来看几个关键点,首先元素之间是有顺序的,并且第一个元素无前驱,最后一个元素无后继,其他的元素都有且只有一个前驱和后继,这样才能形成线性表。

我们用数学语言来进行定义,若有线性表(a1,a2,….,ai-1,ai,ai+1,…,an),则称表中ai-1是ai的直接前驱元素,ai+1是ai的直接后继元素,a1没有直接前驱元素,an没有直接后继元素。线性表元素的个数定义为线性表的长度,当n=0时的特殊表我们称之为空表。

我们来举几个例子,每年的星座列表是不是线性表?当然是,因为星座以白羊打头,双鱼收尾,其中的都有且只有一个前驱和后继,所以完全符合线性表的条件。 
那么公司的上下级关系是不是线性表?当然不是,因为一个上级会有很多个下级,每个人不只有一个后继和前驱,不符合线性表的条件。

前面我们明确定义了线性表的概念,与这些概念相关的是线性表应该具有的操作集合。

例如,向表中插入一个数据,或者获取表中的某个元素,删除某个元素,获取某个元素的直接前驱后继等操作,当然对于不同的应用,线性表应该有什么操作都是不同的,完全由程序设计者来决定,当然像增删查等必要的操作基本是每个线性表都需要具有的。

顺序存储结构

线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。

这里写图片描述

说白了,就是在内存中找一块位置,把一定的内存空间占了,然后把相同数据类型的数据元素按顺序存放在这儿。说到这儿,我们可能有人会想起来,和数组怎么那么像呢?对,线性表的每个数据元素类型都相同,所以我们完全可以通过数组来实现线性表的顺序存储结构

如果我们用数组来实现的话,我们就必须需要两个属性:线性表的长度,需要的数组长度。并且要保证线性表的长度要小于数组的长度,因为我们可能还需要执行插入操作。

java顺序结构线性表的实现

public class LinearList<T>  {
    /**
     * 对象数组,用数组来存放线性表的数据
     */
    protected Object[] element;

    /**
     * 线性表长度,记载元素个数
     */
    protected int len;

    /**
     * 默认构造方法,创建默认容量的空表
     */
    public LinearList() {
        this(64);
    }

    /**
     * 构造方法,创建容量为size的空表
     * 
     * @param size
     */
    public LinearList(int size) {
        this.element = new Object[size];
        this.len = 0;
    }

    /**
     * 判断顺序表是否空,若空返回true,O(1)
     */
    public boolean isEmpty() {
        return this.len == 0;
    }

    /**
     * 返回顺序表长度,O(1)
     */
    public int length() {
        return this.len;
    }

    /**
     * 返回第i(≥0)个元素。若i<0或大于表长则返回null,O(1)
     */

    public T get(int i) {
        if (i >= 0 && i < this.len) {
            return (T) this.element[i];
        }
        return null;
    }

    /**
     * 设置第i(≥0)个元素值为x。若i<0或大于表长则抛出序号越界异常;若x==null,不操作
     */
    public void set(int i, T x) {
        if (x == null)
            return;

        if (i >= 0 && i < this.len) {
            this.element[i] = x;
        } else {
            throw new IndexOutOfBoundsException(i + ""); // 抛出序号越界异常
        }
    }

    /**
     * 顺序表的插入操作 插入第i(≥0)个元素值为x。若x==null,不插入。 若i<0,插入x作为第0个元素;若i大于表长,插入x作为最后一个元素 
     * 思路:从最后一个元素往前遍历到第i个位置,分别将他们都往后移一位
     */
    public void insert(int i, T x) {
        if (x == null)
            return;

        // 若数组满了,则扩充数组容量
        if (this.len == element.length) {
            // temp也引用elements数组
            Object[] temp = this.element;
            // 重新申请一个双倍的数组
            this.element = new Object[temp.length * 2];
            // 复制数组元素,O(n)
            for (int j = 0; j < temp.length; j++) {
                this.element[j] = temp[j];
            }
        }

        // 下标容错
        if (i < 0)
            i = 0;

        if (i > this.len)
            i = this.len;

        // 元素后移,平均移动len/2
        for (int j = this.len - 1; j >= i; j--) {
            this.element[j + 1] = this.element[j];
        }
        this.element[i] = x;
        this.len++;
    }

    /**
     * 在顺序表最后插入x元素
     */
    public void append(T x) {
        insert(this.len, x);
    }

    /**
     * 顺序表的删除操作 删除第i(≥0)个元素,返回被删除对象。若i<0或i大于表长,不删除,返回null。
     * 思路:从删除元素的位置到最后遍历,每一个元素向前移一位
     */

    public T remove(int i) {
        if (this.len == 0 || i < 0 || i >= this.len) {
            return null;
        }

        T old = (T) this.element[i];
        // 元素前移,平均移动len/2
        for (int j = i; j < this.len - 1; j++) {
            this.element[j] = this.element[j + 1];
        }
        this.element[this.len - 1] = null;
        this.len--;
        return old;
    }

    /**
     * 删除线性表所有元素
     */
    public void removeAll() {
        this.len = 0;
    }

    /**
     * 查找,返回首次出现的关键字为key元素
     */
    @SuppressWarnings("unchecked")
    public T search(T key) {
        int find = this.indexOf(key);
        return find == -1 ? null : (T) this.element[find];
    }

    /**
     * 顺序表比较相等 比较两个顺序表是否相等 ,覆盖Object类的equals(obj)方法,O(n)
     */

    public boolean equals(Object obj) {
        if (this == obj)
            return true;

        if (obj instanceof LinearList) {
            LinearList<T> list = (LinearList<T>) obj;
            // 比较实际长度的元素,而非数组容量
            for (int i = 0; i < this.length(); i++) {
                if (!this.get(i).equals(list.get(i))) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }


    // 返回顺序表所有元素的描述字符串,形式为“(,)”,覆盖Object类的toString()方法
    public String toString() {
        String str = "(";
        if (this.len > 0)
            str += this.element[0].toString();
        for (int i = 1; i < this.len; i++)
            str += ", " + this.element[i].toString();
        return str + ") "; // 空表返回()
    }
}

  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178

上面是我们实现的顺序结构存储的线性表,我们已经发现了插入和删除是最麻烦的,步骤最多,需要很多遍历,我们来分析一下这两个操作的时间复杂度。 
如果要插入的或者删除的是最后一个元素,那么不需要移动任何元素,这时候为O(1). 
如果要插入的或者删除的是第一个元素,那么所有元素都需要移动,这时候为O(n). 
而当执行存储和查询的时候,时间复杂度都为O(1),这也间接的说明了一个问题,它比较适合元素个数不变化的存取操作,而插入和删除很不方便

线性表顺序结构存储的优缺点

  • 优点:可以快速的存取表中任意位置的数据
  • 缺点:插入和删除需要移动元素,花费大量时间,并且当数组扩容时,难以确定存储空间的容量,容易造成存储空间的碎片化。-

链式存储结构

前面我们已经证明了,线性表的顺序存储结构有一个很大的缺点,就是插入和删除操作时需要消耗大量时间,能不能想个办法解决一下呢?

首先我们需要思考一下为什么会造成这个情况,仔细思考其实是因为两个相邻元素之间在内存中也是紧挨着的,如果要插入自然要向后移,否则会出现覆盖,要删除,后面的元素自然也要向前移。那么我们应该怎么解决这一情况呢?我们可不可以让逻辑上相邻的两个元素,在内存中其实不是相邻的,而只需前一个元素知道后一个元素的地址,然后连起来就行了?对,这就是我们要讲的链式存储结构。

这里写图片描述

通常讲采用链式存储结构的线性表称为线性链表,从链接方式来看,链表可分为单链表、循环链表和双链表。从实现角度来看,链表分为动态链表和静态链表。

链表用一组任意的存储单元来存储线性表的元素,这组存储单元可以连续也可以不连续,所以现在每个元素中除了要存储数据,还要存储其后继元素的存储地址。我们把存储数据元素信息的域称为数据域,把存储后继元素位置的域称为指针域,这两部分共同组成一个数据元素单元,我们把它叫做结点(Node)。

单链表

每个结点中的指针域只包含下一个结点的位置,叫做单链表。单链表的最后一个结点指向为null。

这里写图片描述

单链表的java实现


首先,我们要实现一个结点类Node 

public class Node<T> {
    /**
     * 数据域,保存数据元素
     */
    public T data;
    /**
     * 地址域,引用后继结点
     */
    public Node<T> next;
    /**
     * 构造节点
     */
    public Node() {
        this(null, null);
    }

    /**
     * 构造结点,data指定数据元素,next指定后继结点
     * 
     * @param data
     * @param next
     */
    public Node(T data, Node<T> next) {
        this.data = data;
        this.next = next;
    }

    /**
     * 返回结点元素值对应的字符串
     */

    public String toString() {
        return this.data.toString();
    }

}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
public class SingleLinkedList<T> {
        /**
         * 指定头结点
         */
        public Node<T> head;

        /**
         * 默认构造方法,构造空单链表
         */
        public SingleLinkedList() {
            // 创建头结点,data和next值均为null
            this.head = new Node<T>();
        }

        /**
         * 由指定数组中的多个对象构造单链表。采用尾插入构造单链表
         * 头插入就是每次新的结点在前面,例如第一个结点在头结点的前面,以此类推
         * 头插入不太符合我们逻辑,所以尾插入,生成的结点在后面
         * 
         * 若element==null,Java将抛出空对象异常;若element.length==0,构造空链表
         * 思路:让一个结点指向最后一个结点,刚开始就一个头结点,然后循环遍历数组,将最后一个结点的地址域设置为数组的元素
         * 
         * @param element
         */
        public SingleLinkedList(T[] element) {
            // 创建空单链表,只有头结点
            this();
            // last指向单链表最后一个结点
            Node<T> last = this.head;
            for (int i = 0; i < element.length; i++) {
                last.next = new Node<T>(element[i], null);
                last = last.next;
            }
        }

        /**
         * 判断单链表是否空,O(1)
         * 判断头节点的地址域是否为空即可
         */

        public boolean isEmpty() {
            return this.head.next == null;
        }

        /**
         * 返回单链表长度,O(n), 基于单链表遍历算法
         * 数组中长度定义时就确定,单链表就必须得从头开始向后计数
         */

        public int length() {
            //定义i用来计数
            int i = 0;
            // p从单链表第一个结点开始
            Node<T> p = this.head.next;
            // 若单链表未结束
            while (p != null) {
                i++;
                // p到达后继结点
                p = p.next;
            }
            return i;
        }

        /**
         * 获得第i(≥0)个元素,若i<0或大于表长则返回null,O(n)
         */

        public T get(int i) {
            if (i >= 0) {
                //定义一个头结点,从头开始查找
                Node<T> p = this.head.next;
                //如果j在跳出循环时不为空,说明i处元素存在,否则说明元素不存在
                for (int j = 0; p != null && j < i; j++) {
                    p = p.next;
                }

                // p指向第i个结点
                if (p != null) {
                    return p.data;
                }
            }
            return null;
        }

        /**
         * 设置第i(≥0)个元素值为x。若i<0或大于表长则抛出序号越界异常;若x==null,不操作。O(n)
         */

        public void set(int i, T x) {
            if (x == null)
                return;
            //还是从头结点开始
            Node<T> p = this.head.next;
            for (int j = 0; p != null && j < i; j++) {
                p = p.next;
            }

            //如果插入的位置>0,并且循环后的p不为null,则赋值给这个结点
            if (i >= 0 && p != null) {
                p.data = x;
            } else {
                throw new IndexOutOfBoundsException(i + "");
            }
        }

        /**
         * 插入第i(≥0)个元素值为x。若x==null,不插入。 若i<0,插入x作为第0个元素;若i大于表长,插入x作为最后一个元素。O(n)
         * 初始化从第一个结点开始,当j<i时遍历节点,若到末尾结点为空,说明第i个元素不存在
         * 如果存在,就创建一个新结点,然后相互赋值即可
         */

        public void insert(int i, T x) {
            // 不能插入空对象
            if (x == null) {
                return;
            }

            // p指向头结点
            Node<T> p = this.head;
            // 寻找插入位置
            for (int j = 0; p.next != null && j < i; j++) {
                // 循环停止时,p指向第i-1结点或最后一个结点
                p = p.next;
            }
            // 插入x作为p结点的后继结点,如果i<0,则p还是为头结点,所以是在最前面插入
            //如果i>链表长度,则p指向最后一个元素,所以是插入到最后
            p.next = new Node<T>(x, p.next);
        }

        /**
         * 在单链表最后添加x对象,O(n)
         * 直接定义一个比较大的数,然后插入的时候会遍历,当结点为空时说明是最后,然后跳出循环
         * 直接插入到最后
         */

        public void append(T x) {
            insert(Integer.MAX_VALUE, x);
        }

        /**
         * 删除第i(≥0)个元素,返回被删除对象。若i<0或i大于表长,不删除,返回null。O(n)
         * 思路:从头结点开始遍历,如果遍历结束,结点为空,则说明第i个元素不存在,否则就查找成功
         * 然后将删除元素的地址域赋值给前一个元素即可,然后返回删除元素的数据
         */

        public T remove(int i) {
            if (i >= 0) {
                Node<T> p = this.head;
                for (int j = 0; p.next != null && j < i; j++) {
                    //因为我们是从链表元素是从0开始,所以p并不是我们要删除的元素,p的下一个才是
                    p = p.next;
                }

                if (p != null) {
                    // 获得原对象
                    T old = p.next.data;
                    // 删除p的后继结点
                    p.next = p.next.next;
                    return old;
                }
            }
            return null;
        }

        /**
         * 删除单链表所有元素 Java将自动收回各结点所占用的内存空间 
         * 我们只需要将头结点设为null,其他的jvm会帮我们回收垃圾
         */

        public void removeAll() {
            this.head.next = null;
        }

        /**
         * 顺序查找关键字为key元素,返回首次出现的元素,若查找不成功返回null
         * key可以只包含关键字数据项,由T类的equals()方法提供比较对象相等的依据
         */

        public T search(T key) {
            if (key == null)
                return null;
            for (Node<T> p = this.head.next; p != null; p = p.next){
                    if (p.data.equals(key))
                        return p.data;
                   } 
            return null;
        }

        /**
         * 返回单链表所有元素的描述字符串,形式为“(,)”,覆盖Object类的toString()方法,O(n)
         */

        public String toString() {
            String str = "(";
            for (Node<T> p = this.head.next; p != null; p = p.next) {
                str += p.data.toString();
                if (p.next != null)
                    str += ","; // 不是最后一个结点时后加分隔符
            }
            return str + ")"; // 空表返回()
        }

        /**
         * 比较两条单链表是否相等
         */

        public boolean equals(Object obj) {
            if (obj == this)
                return true;

            if (obj instanceof SingleLinkedList) {
                SingleLinkedList<T> list = (SingleLinkedList<T>) obj;
                return equals(this.head.next, list.head.next);
            }
            return false;
        }

        /**
         * 比较两条单链表是否相等,递归方法
         * 
         * @param p
         * @param q
         * @return
         */
        private boolean equals(Node<T> p, Node<T> q) {
            return p == null && q == null || p != null && q != null
                    && p.data.equals(q.data) && equals(p.next, q.next);
        }
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229

单链表的优缺点

通过代码的编写,我们发现单链表的查找方法时间复杂度为O(n),而插入和删除为O(1)

  • 优点:执行插入和删除效率高,不需要事先分配空间,元素个数不受限制,不会浪费空间

  • 缺点:查找效率比较低

我们可以得出一些结论,若线性表需要频繁查找,很少进行插入和删除操作时,则使用顺序存储结构。反之,需要大量的插入和删除时,则使用链式存储结构。

循环链表

循环链表是一个首尾相连的链表,将单链表最后一个结点的地址域改为指向表头结点,就得到了单链循环链表,称为循环单链表。

循环单链表和单链表的主要差异就在循环的判断条件上,单链表判断循环结束是看地址域是否为空,而循环单链表则是判断地址域是否是头结点。在前面的单链表中,我们总是用一头结点来指向我们的第一个数据元素,这样非常方便。在单链表中,我们有了头结点,可以很快的访问到第一个元素,访问最后一个元素却需要O(n),循环链表就可以解决这个问题,使访问第一个元素和最后一个元素都是O(1),怎么实现呢?再添加一个指向尾部的结点。

循环单链表的实现


public class CircleSingleLinkedList<T> {
        /**
         * 头指针,指向循环单链表的头结点
         * 尾指针,指向循环单链表的最后一个结点
         */
        public Node<T> head;
        public Node<T> tail;

        /**
         * 默认构造方法,构造空循环单链表
         * 即让自己指向自己
         * 然后让尾指针指向最后一个结点,此时也是指向头结点
         */
        public CircleSingleLinkedList() { 
            this.head = new Node<T>();
            this.head.next = this.head;
            this.tail=new Node<T>();
            this.tail.next=this.head;
        }

        /**
         * 判断循环单链表是否空
         * 判断头结点的地址域是不是自己即可
         */

        public boolean isEmpty() {
            return this.head.next == this.head;
        }

        /**
         * 返回循环单链表长度,单链表遍历算法,O(n)
         * 注意我们说的判断条件的区别
         */

        public int length() {
            int i = 0;
            for (Node<T> p = this.head.next; p != this.head; p = p.next) {
                i++;
            }
            return i;
        }

        /**
         * 返回第i(≥0)个元素,若i<0或大于表长则返回null,O(n)
         */

        public T get(int i) {
            if (i >= 0) {
                Node<T> p = this.head.next;
                for (int j = 0; p != this.head && j < i; j++)
                    p = p.next;
                if (p != this.head)
                    return p.data; // p指向第i个结点
            }
            return null;
        }

        /**
         * 设置第i(≥0)个元素值为x。若i<0或大于表长则抛出序号越界异常;若x==null,不操作。O(n)
         */

        public void set(int i, T x) {
            // 不能设置空对象
            if (x == null)
                return;
            Node<T> p = this.head.next;
            for (int j = 0; p != this.head && j < i; j++)
                p = p.next;
            // p指向第i个结点
            if (i >= 0 && p != this.head)
                p.data = x;
            else
                throw new IndexOutOfBoundsException(i + ""); // 抛出序号越界异常
        }


        public void insert(int i, T x) {
            // 不能插入空对象
            if (x == null) 
                return;

            // p指向头结点
            Node<T> p = this.head;
            // 寻找插入位置
            for (int j = 0; p.next != this.head && j < i; j++) {
                // 循环停止时,p指向第i-1结点或最后一个结点
                p = p.next;
            }
            // 插入x作为p结点的后继结点,包括头插入(i<=0)、中间/尾插入(i>0) 
            p.next = new Node<T>(x, p.next);
            //每插入一个都要保证尾结点指向最后一个元素
            this.tail.next=p.next;
        }


        public void append(T x) {
            insert(Integer.MAX_VALUE, x);
        }

        /**
         * 删除第i(≥0)个元素,返回被删除对象。若i<0或i大于表长,不删除,返回null。O(n)
         */

        public T remove(int i) {
            if (i >= 0) {
                Node<T> p = this.head;
                for (int j = 0; p.next != this.head && j < i; j++) {
                    p = p.next;
                }

                if (p != null) {
                    // 获得原对象
                    T old = p.next.data;
                    // 删除p的后继结点
                    p.next = p.next.next;
                    return old;
                }
            }
            return null;
        }

        /**
         * 删除单链表所有元素 Java将自动收回各结点所占用的内存空间
         */

        public void removeAll() {
            this.head.next = this.head;
        }

        /**
         * 顺序查找关键字为key元素,返回首次出现的元素,若查找不成功返回null
         * key可以只包含关键字数据项,由T类的equals()方法提供比较对象相等的依据
         */

        public T search(T key) {
            if (key == null)
                return null;
            for (Node<T> p = this.head.next; p != this.head; p = p.next)
                if (p.data.equals(key))
                    return p.data;
            return null;
        }

        /**
         * 返回循环单链表所有元素的描述字符串
         */

        public String toString() {
            String str = "(";
            Node<T> p = this.head.next;
            // 遍历单链表的循环条件改变了
            while (p != this.head) {
                str += p.data.toString();
                if (p != this.head)
                    str += ", "; // 不是最后一个结点时后加分隔符
                p = p.next;
            }
            return str + ")";
        }

        /**
         * 比较两条单链表是否相等
         */
        @SuppressWarnings("unchecked")

        public boolean equals(Object obj) {
            if (obj == this)
                return true;

            if (obj instanceof CircleSingleLinkedList) {
                CircleSingleLinkedList<T> list = (CircleSingleLinkedList<T>) obj;
                return equals(this.head.next, list.head.next);
            }
            return false;
        }

        /**
         * 比较两条单链表是否相等,递归方法
         * 
         * @param p
         * @param q
         * @return
         */
        private boolean equals(Node<T> p, Node<T> q) {
            return p == null && q == null || p != null && q != null
                    && p.data.equals(q.data) && equals(p.next, q.next);
        }  

        //合并两条循环单链表 
        /*有了尾结点这个工作将变得无比轻松,我们只需要让A链表的尾部指向B链表的第一个结点(不是头结点)
        然后让B链表的尾结点指向A链表的头结点即可
        */
        public void merge(CircleSingleLinkedList t) {
             Node <T> n=this.tail.next.next;
             this.tail.next.next=t.tail.next.next.next;
             t.tail.next.next=n;
         }
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199

双向链表

我们在单链表中如果要查找上一个结点的话,那就得从头开始循环了,最坏的时间复杂度就是O(n), 
你会想这也太麻烦了,我能不能像next直接查找下一个结点一样,设计链表可以直接查找上一个结点呢?当然可以,为了克服上述问题,前辈们设计了双向链表。

双向链表:就是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。所以在双向链表中的每个结点都有两个指针域,一个指向直接后继,一个指向直接前驱。

既然单链表可以有循环链表,那么双向链表当然也可以循环。

双向循环链表为空时

这里写图片描述

双向循环链表不为空时

这里写图片描述

双向循环链表的实现

//先创建一个指向前驱和后继结点的结点对象

public class DLinkNode<T> {
     public T data;

        /**
         * pred指向前驱结点,next指向后继结点
         */
        public DLinkNode<T> pred;
        public DLinkNode<T> next;

        /**
         * 构造节点,data指定元素,pred指向前驱节点,next指向后继节点
         */
        public DLinkNode(T data, DLinkNode<T> pred, DLinkNode<T> next) {
            this.data = data;
            this.pred = pred;
            this.next = next;
        }
        /**
         * 默认构造器
         */
        public DLinkNode() {
            this(null, null, null);
        }
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
//实现双向循环链表

public class CircleDoubleLinkedList<T> { 
    //头结点
    public DLinkNode<T> head;

    //默认构造方法
    public CircleDoubleLinkedList() {
        this.head=new DLinkNode<T>();
        this.head.pred=this.head;
        this.head.next=this.head;
    } 

    //将数组构造成循环双链表
    public CircleDoubleLinkedList(T[] element) {
        this(); 
        //rear指向最后的结点
        DLinkNode<T> rear=this.head;
        for(int i=0;i<element.length;i++) {
            rear.next=new  DLinkNode<T>(element[i],rear,this.head);
            rear=rear.next;
        } 
        //将头结点的直接前驱结点设置为最后一个结点
        this.head.pred=rear;
    }


        public boolean isEmpty() {
            return this.head.next == this.head;
        }

        /**
         * 获取链表的长度
         */
        public int length() {
            int i = 0;
            for (DLinkNode<T> p = this.head.next; p != this.head; p = p.next) {
                i++;
            }
            return i;
        }

        /**
         * 返回第i(≥0)个元素,若i<0或大于表长则返回null,
         */

        public T get(int i) {
            if (i >= 0) {
                DLinkNode<T> p = this.head.next;
                for (int j = 0; p != this.head && j < i; j++) {
                    p = p.next;
                }

                // p指向第i个结点
                if (p != null) {
                    return p.data;
                }
            }
            return null;
        }

        /**
         * 设置第i(≥0)个元素值为x。若i<0或大于表长则抛出序号越界异常;若x==null,不操作。O(n)
         */

        public void set(int i, T x) {
            if (x == null)
                return;

            DLinkNode<T> p = this.head.next;
            for (int j = 0; p != this.head && j < i; j++) {
                p = p.next;
            }

            // p指向第i个结点
            if (i >= 0 && p != null) {
                p.data = x;
            } else {
                throw new IndexOutOfBoundsException(i + "");
            }
        }

        /**
         * 插入第i(≥0)个元素值为x。若x==null,不插入。 若i<0,插入x作为第0个元素;若i大于表长,插入x作为最后一个元素。O(n)
         */

        public void insert(int i, T x) {
            if (x == null)
                return;
            DLinkNode<T> p = this.head;
            // 寻找插入位置 循环停止时,p指向第i-1个结点
            for (int j = 0; p.next != this.head && j < i; j++) {
                p = p.next;
            }
            // 插入在p结点之后,包括头插入、中间插入
            DLinkNode<T> q = new DLinkNode<T>(x, p, p.next);
            p.next.pred = q;
            p.next = q;
        }

        /**
         * 在循环双链表最后添加结点,O(1)
         */
        public void append(T x) {
            if (x == null)
                return;
            // 插入在头结点之前,相当于尾插入
            DLinkNode<T> q = new DLinkNode<T>(x, head.pred, head);
            head.pred.next = q;
            head.pred = q;
        }

        /**
         * 删除第i(≥0)个元素,返回被删除对象。若i<0或i大于表长,不删除,返回null。O(n)
         */

        public T remove(int i) {
            if (i >= 0) {
                DLinkNode<T> p = this.head.next;
                // 定位到待删除结点
                for (int j = 0; p != this.head && j < i; j++) {
                    p = p.next;
                }

                if (p != head) {
                    // 获得原对象
                    T old = p.data;
                    // 删除p结点自己
                    p.pred.next = p.next;
                    p.next.pred = p.pred;
                    return old;
                }
            }
            return null;
        }

        /**
         * 删除循环双链表所有元素
         */

        public void removeAll() {
            this.head.pred = this.head;
            this.head.next = this.head;
        }

        /**
         * 顺序查找关键字为key元素,返回首次出现的元素,若查找不成功返回null
         * key可以只包含关键字数据项,由T类的equals()方法提供比较对象相等的依据
         */

        public T search(T key) {
            if (key == null)
                return null;
            for (DLinkNode<T> p = this.head.next; p != this.head; p = p.next)
                if (key.equals(p.data))
                    return p.data;
            return null;
        }

        /**
         * 返回循环双链表所有元素的描述字符串,循环双链表遍历算法,O(n)
         */

        public String toString() {
            String str = "(";
            for (DLinkNode<T> p = this.head.next; p != this.head; p = p.next) {
                str += p.data.toString();
                if (p.next != this.head)
                    str += ",";
            }
            return str + ")"; // 空表返回()
        }

        /**
         * 比较两条循环双链表是否相等,覆盖Object类的equals(obj)方法
         */
        @SuppressWarnings("unchecked")
        public boolean equals(Object obj) {
            if (obj == this)
                return true;
            if (!(obj instanceof CircleDoubleLinkedList))
                return false;
            DLinkNode<T> p = this.head.next;
            CircleDoubleLinkedList<T> list = (CircleDoubleLinkedList<T>) obj;
            DLinkNode<T> q = list.head.next;
            while (p != head && q != list.head && p.data.equals(q.data)) {
                p = p.next;
                q = q.next;
            }
            return p == head && q == list.head;
        }
}    

  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193

双向循环链表就是典型的用空间换时间。

JAVA API中的表实现

JAVA的类库中,包含着很多数据结构的实现,设计的很精妙,所以说JDK源码就是我们非常好的学习工具。Collection接口中有很多使用我们数据结构来实现的集合类,我们今天就来看看集合中的那些表实现。

Collecation接口中定义了很多集合类中应该有的方法,并且实现了Iterable接口,实现Iterable接口的类可以使用增强for循环。

实现Iterable接口的集合必须提供Iterator方法,该方法返回一个Iterator对象,Iterator也是java.util包中的一个接口,Iterator接口主要是为了遍历集合,将当前的位置在对象内部存储起来,然后就可以进行遍历。当一个增强for循环在循环一个实现Iterable接口的对象时,其实增强for循环内部就是使用的Iterator对象在进行遍历。

好了,说了这么多,我们扯得有点远,我们这次最大的目的是来看看jdk中的表实现,它由java.util包中的List接口指定,它下面有很多实现类,例如ArrayList类就是顺序结构线性表的实现,存取指定位置元素比较方便,而插入和删除效率就很低。LinkedList类则是双链表的实现,存取指定位置元素效率比较低,而插入和删除的效率则高于ArrayList。

尽管前面我们已经知道了顺序结构线性表和链表的差别,这里我们再来看一下ArrayList和LinkedList的差别。

  • 不论是对于ArrayList还是LinkedList而言,在后端添加N个元素的操作运行时间都是O(n).

  • 如果从集合前面添加N个元素的话,,LinkedList的运行时间是O(n),而ArrayList每添加一个就是O(n),添加n个则是O(n^2)

  • 对于通过get()计算集合中n个数的和的方法,ArrayList的运行时间是O(n),LinkedList则为O(n^2),但是如果使用迭代器的话,那么任意的List集合运行时间都是O(n),因为迭代器会记录当前 
    位置。

小例子:remove方法对LinkedList的使用

我们来做一个例子,将一个表中的所有偶数都删除,我们当然不可能使用ArrayList,因为每次操作都是O(n),所以我们要使用LinkedList..

我们先来一种我们正常思考的算法。

    public void removeEven(LinkedList<Integer> lst) {
        int i=0;
        while(i<lst.size()) {
            if(lst.get(i)%2==0) {
                lst.remove(i);
            }
            i++;
        }
     }

上面是我们最经常想到的方法,但是也暴露出了两个问题,首先LinkedList的get的效率是很低的,为O(n),并且remove方法效率也很低,也是O(n)。所以整个方法的时间为O(n^2) 
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

怎么解决这两个问题呢?我们用我们前面提到的迭代器。

    public void removeEven(LinkedList<Integer> lst) {
        Iterator<Integer> i=lst.iterator();
        while(i.hasNext()) {
            if(i.next()%2==0) {
                i.remove();
            }       
        }
    }  
    使用迭代器的话,删除只需要花费常数时间,因为迭代器就在删除元素这里,所以整个函数只需要O(n)的时间,效率大大提高。


  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

ArrayList的实现

我们前面实现的顺序结构存储的线性表其实就是ArrayList的内部实现,如果有什么模糊的地方可以去前面看看源代码,这里我们来实现点更接近ArrayList的功能,那就是迭代器。

我们先来编写一个迭代器

public class LinearListIterator<T> implements Iterator<T> {

    private int current=0;
    private LinearList<T> linearList;


    public LinearListIterator(LinearList<T> linearList) {
        this.linearList=linearList;
    } 
    //判断当前项有没有大于表的长度
    @Override
    public boolean hasNext() {
        return current<linearList.len;
    }
    //返回当前项,并加1记录当前位置
    @Override
    public T next() {
        return (T) linearList.element[current++];
    } 


}


  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
我们自己编写的ArrayList必须实现Iterable接口,然后实现他的方法,返回一个迭代器对象

@Override
public Iterator<T> iterator() {
    return new LinearListIterator(this);
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
我们测试一下我们的迭代器 
    public static void main(String[] args) {

        LinearList<Integer> l=new LinearList<Integer>();
        l.append(1);
        l.append(2);
        l.append(3);
        l.append(4);
        l.append(5);
        LinearListIterator<Integer> iterator=(LinearListIterator<Integer>) l.iterator(); 
        //可以正常输出
        while(iterator.hasNext()) {
            System.out.println(iterator.next());
        }   
    } 
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

当然ArrayList内部采用的是内部类来实现迭代器,这样有一个很明显的优点就是可以直接操作当前的集合对象,而不用别人再来传递,并且可以获得当前集合的所有属性来操作。

LinkedList的实现 
JDK中的LinkedList是一个双向链表,并且没有循环。我们前面也实现了,具体可以看看前面的代码。 
至于LinkedList迭代器的实现,我想大家有了前面的基础,会很容易的实现。

猜你喜欢

转载自blog.csdn.net/smxjant/article/details/80015180
今日推荐