文章目录
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)相比较顺序表,链表插入和删除的时间复杂度虽然一样,但仍然有很大的优势,因为链表的物理地址是不连续的,它不需要预先指定存储空间大小,或者在存储过程中涉及到扩容等操作,同时它并没有涉及的元素的交换。相比较顺序表,链表的查询操作性能会比较低。因此,如果我们的程序中查询操作比较多,建议使用顺序表,增删操作比较多,建议使用链表