Java数据结构之线性表

本文详细介绍了线性表的概念,包括它的特性、顺序存储结构(顺序表)和链式存储结构(链表)。顺序表以数组实现,优点是节省空间、索引查找高效,但插入删除操作效率低;链表则通过指针链接元素,插入删除灵活但查找较慢。此外,文章还提供了链表的单向链表和双向链表实现,并分析了它们的时间复杂度。
摘要由CSDN通过智能技术生成

1.什么是线性表

线性表是n个相同数据类型元素的有限序列,通常记作(a0,a1,…an-1)
(1)相同数据类型元素:都是具有相同属性的元素
比如都是数字,都是字符,也可以都是具有复杂结构的数据元素(如学生、商品等)
(2)有限:
线性表中数据元素的个数n定义为线性表的长度,n是一个有限值
①当n=0时线性表为空表
②在非空的线性表每个数据元素在线性表中都有唯一确定的序号
③在一个具有n > 0个数据元素的线性表中,数据元素序号的范围是[0,n-1]
(3)序列(顺序性)
在线性表的相邻数据元素之间存在着序偶关系,即ai-1是ai的直接前驱,ai是ai-1的直接后继;同时,ai又是ai+1的直接前驱,ai+1是ai的直接后继
①唯一没有直接前驱的元素a0一端称为表头
②唯一没有后继的元素an-1一端称为表尾
③除了表头和表尾元素外,任何一个元素都有且仅有一个直接前驱和直接后继

2.线性表的存储结构

2.1顺序表—顺序存储结构

2.1.1 特点

在内存中分配连续的空间,只存储数据,不需要存储地址信息。位置就隐含着地址。java中ArrayList集合的底层是一种顺序表

2.1.2 优缺点

(1)优点:
a.节省存储空间,因为分配给数据的存储单元全用来存放结点的数据,结点之间的逻辑关系没有占用额外的存储空间
b.索引查找效率高,即每一个结点对应一个序号,由该序号可以直接计算出结点的存储地址(顺序存储结构中元素类型是一致的,每个
元素占用的内存长度是相同的,下标访问(随机访问)效率高)
(2)缺点:
a.插入和删除操作需要移动元素,效率较低
b.必须提前分配固定数量的空间,如果存储元素少,可能导致空间浪费
c.按照内容查询效率较低,因为需要逐个比较判断

在这里插入图片描述

2.1.3 顺序表的时间复杂度

(1)get(i):不难看出,不论数据元素量N有多大,只需要一次eles[i]就可以获取到对应的元素,所以时间复杂度为O(1);
(2)insert(int i,T t):每一次插入,都需要把i位置后面的元素移动一次,随着元素数量N的增大,移动的元素也越多,时间复杂为O(n);
(3)remove(int i):每一次删除,都需要把i位置后面的元素移动一次,随着数据量N的增大,移动的元素也越多,时间复杂度为O(n);
(4)由于顺序表的底层由数组实现,数组的长度是固定的,所以在操作的过程中涉及到了容器扩容操作。这样会导致顺序表在使用过程中的时间复杂度不是线性的,在某些需要扩容的结点处,耗时会突增,尤其是元素越多,这个问题越明显

2.2链表—链式存储结构

2.2.1 特点

①数据元素的存储对应的是不连续的存储空间,每个存储结点对应一个需要存储的数据元素。
②每个结点是由数据域和指针域组成的,元素之间的逻辑关系通过存储结点之间的链接关系反映出来
③逻辑上相邻的结点物理上未必相邻
(2)优缺点
①优点
a.插入、删除灵活(不必移动结点,只要改变结点中的指针,但是需要先定位到元素上)
b.有元素才会分配结点空间,不会有闲置的结点。
②缺点
a.比顺序存储结构的存储密度小(每个结点都由数据域和指针域组成,所以相同空间内,假设全存满的话顺序比链式存储更多)
b.查找结点时链式存储要比顺序存储慢(每个结点地址不连续、无规律,按照索引查询需要一个一个的找)
(3)结点类的API设计

public class Node<T> {
	//存储元素
	public T item;
 	//指向下一个结点
	public Node next;
	public Node(T item, Node next) {
 		this.item = item;
 		this.next = next;
 	}
}

2.2.1 单向链表

(1)概念
单向链表是链表的一种,它由多个结点组成,每个结点都由一个数据域和一个指针域组成,数据域用来存储数据,指针域用来指向其后继结点。链表的头结点的数据域不存储数据,指针域指向第一个真正存储数据的结点。

在这里插入图片描述
(2)单向链表API设计
在这里插入图片描述

(3) 单向链表代码实现

//单向列表代码
import java.util.Iterator;
public class LinkList<T> implements Iterable<T> {
	//记录头结点
	private Node head;
 	//记录链表的长度
	private int N;
 	public LinkList(){
 		//初始化头结点
 		head = new Node(null,null);
 		N=0;
 	}
 	//清空链表
	public void clear(){
		head.next=null;
		  head.item=null;
        N=0;
    }
 
    //获取链表的长度
    public int length(){
        return N;
    }
 
    //判断链表是否为空
    public boolean isEmpty(){
        return N==0;
    }
 
    //获取指定位置i出的元素
    public T get(int i){
        if (i<0||i>=N){
            throw new RuntimeException("位置不合法!");
        }
        Node n = head.next;
        for (int index = 0; index < i; index++) {
            n = n.next;
        }
        return n.item;
    }
    
    //向链表中添加元素t
    public void insert(T t){
        //找到最后一个节点
        Node n = head;
        while(n.next!=null){
            n = n.next;
        }
        Node newNode = new Node(t, null);
        n.next = newNode;
         //链表长度+1
        N++;
    }
 
    //向指定位置i处,添加元素t
    public void insert(int i,T t){
        if (i<0||i>=N){
            throw new RuntimeException("位置不合法!");
        }
 
        //寻找位置i之前的结点
        Node pre = head;
        for (int index = 0; index <=i-1; index++) {
            pre = pre.next;
        }
        //位置i的结点
        Node curr = pre.next;
        //构建新的结点,让新结点指向位置i的结点
 		Node newNode = new Node(t, curr);
        //让之前的结点指向新结点
        pre.next = newNode;
        //长度+1
        N++;
    }
 
    //删除指定位置i处的元素,并返回被删除的元素
    public T remove(int i){
        if (i<0 || i>=N){
            throw new RuntimeException("位置不合法");
        }
 
        //寻找i之前的元素
        Node pre = head;
        for (int index = 0; index <=i-1; index++) {
            pre = pre.next;
        }
 
        //当前i位置的结点
        Node curr = pre.next;
        //前一个结点指向下一个结点,删除当前结点
        pre.next = curr.next;
        //长度-1
        N--;
        return curr.item;
    }
 
    //查找元素t在链表中第一次出现的位置
    public int indexOf(T t){
        Node n = head;
        for (int i = 0;n.next!=null;i++){
            n = n.next;
            if (n.item.equals(t)){
                return i;
            }
        }
        return -1;
    }
 
 
    //结点类
    private class Node{
 
        //存储数据
        T item;
        //下一个结点
        Node next;
 
        public Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }
         }
 
    @Override
    public Iterator iterator() {
        return new LIterator();
    }
 
    private class LIterator implements Iterator<T>{
        private Node n;
 
        public LIterator() {
            this.n = head;
        }
 
        @Override
        public boolean hasNext() {
            return n.next!=null;
        }
 
        @Override
        public T next() {
            n = n.next;
            return n.item;
        }
    }
 
}
//测试代码
public class Test {
    public static void main(String[] args) throws Exception {
 
        LinkList<String> list = new LinkList<>();
        list.insert(0,"张三");
        list.insert(1,"李四");
        list.insert(2,"王五");
        list.insert(3,"赵六");
        //测试length方法
        for (String s : list) {
            System.out.println(s);
        }
        System.out.println(list.length());
        System.out.println("-------------------");
        //测试get方法
        System.out.println(list.get(2));
        System.out.println("------------------------");
        //测试remove方法
        String remove = list.remove(1);
        System.out.println(remove);
        System.out.println(list.length());
        System.out.println("----------------");;
        for (String s : list) {
            System.out.println(s);
        }
    }
}

2.2.2 双向链表

(1)概念
双向链表也叫双向表,是链表的一种,它由多个结点组成,每个结点都由一个数据域和两个指针域组成,数据域用来存储数据,其中一个指针域用来指向其后继结点,另一个指针域用来指向前驱结点。链表的头结点的数据域不存储数据,指向前驱结点的指针域值为null,指向后继结点的指针域指向第一个真正存储数据的结点。

在这里插入图片描述

(2)双向链表API设计
在这里插入图片描述

2.2.3 链表的复杂度分析

(1)get(int i):每一次查询,都需要从链表的头部开始,依次向后查找,随着数据元素N的增多,比较的元素越多,时间复杂度为O(n)
(2)insert(int i,T t):每一次插入,需要先找到i位置的前一个元素,然后完成插入操作,随着数据元素N的增多,查找的元素越多,时间复杂度为O(n);
(3)remove(int i):每一次移除,需要先找到i位置的前一个元素,然后完成插入操作,随着数据元素N的增多,查找的元素越多,时间复杂度为O(n)
(4)相比较顺序表,链表插入和删除的时间复杂度虽然一样,但仍然有很大的优势,因为链表的物理地址是不连续的,它不需要预先指定存储空间大小,或者在存储过程中涉及到扩容等操作,同时它并没有涉及的元素的交换。相比较顺序表,链表的查询操作性能会比较低。因此,如果我们的程序中查询操作比较多,建议使用顺序表,增删操作比较多,建议使用链表

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值