一、什么是线性表
线性表是具有相同特性的数据元素的一个有限序列。常见的线性表有:顺序表、链表、数组、字符串、栈、队列等。像我们之前所接触的ArrayList
就属于顺序表,而LinkedList
就属于链表。
- 针对有限序列这几个字有以下几点说明:
- 该序列中所含元素的个数叫做线性表的长度,如果用
n
表示线性表的长度,那么n的取值范围应该是n>=0
,当n=0
时,表示线性表是个空表,即表中无任何元素。 - 该序列中第i个元素为
ai
(1<=i<=n),线性表的一般表示为:(a1, a2, a3, a4,...an)
。 - 其中
a1
为第一个元素,又称为表头元素,a2
为第二个元素,an
为最后一个元素,又称为表尾元素。 - 线性表的特点:
① 有序性:线性表里的元素是一个挨着一个按照顺序排列下去的。
② 线性表是允许没有元素的,即是个空表。
③ 每个元素有且仅有一个前驱和一个后继(第一个元素和最后一个元素除外,第一个元素只有一个后继,最后一个元素只有一个前驱)。
二、什么是顺序表
线性表只是一种逻辑结构,至于它在计算机中是如何存储的,我们还需要专门来讨论。
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构。一般情况下是通过数组实现的。即逻辑上和物理上都是连续的。
- 顺序表一般分为:
- 静态顺序表:使用定长数组存储。
- 动态顺序表:使用动态开辟的数组存储,增容一般是呈2倍的增长,而且需要申请新空间,拷贝数据,释放旧空间,因此会有不小的消耗。
package com.xxx;
public interface ISequence {
//在pos位置插入data
boolean add(int pos, Object data);
//查找关键字key,找到返回key的下标,没有找到返回-1
int search(Object key);
//查找是否包含关键字key
boolean contains(Object key);
//得到pos位置的值
Object getPos(int pos);
//删除第一次出现的关键字key
Object remove(Object key);
//得到顺序表的长度
int size();
//打印顺序表
void display();
//清空顺序表以防内存泄漏
void clear();
}
package com.xxx;
import java.util.Arrays;
public class MySequenceImpl implements ISequence {
//存放顺序表的数组
private Object[] elem;
//有效数据个数
private int usedSize;
//能存放数据的最大长度
private static final int DEFAULT_SIZE = 10;
public MySequenceImpl() {
this.elem = new Object[DEFAULT_SIZE];
this.usedSize = 0;
}
private boolean isFull() {
return this.usedSize == this.elem.length;
}
private boolean isEmpty() {
return this.usedSize == 0;
}
@Override
public boolean add(int pos, Object data) {
//判断pos的合法性
if(pos < 0 || pos > this.usedSize) {
return false;
}
//判断是否为满
if(isFull()) {
//2倍扩容
this.elem = Arrays.copyOf(this.elem, 2*this.elem.length);
}
//插入
for(int i = usedSize-1; i >= pos; i--) {
this.elem[i+1] = this.elem[i];
}
this.elem[pos] = data;
this.usedSize++;
return true;
}
@Override
public int search(Object key) {
if(isEmpty()) {
return -1;
}
for(int i = 0; i < this.usedSize; i++) {
if(this.elem[i].equals(key)) {
return i;
}
}
return -1;
}
@Override
public boolean contains(Object key) {
if(isEmpty()) {
return false;
}
for(int i = 0; i < this.usedSize; i++) {
if(this.elem[i].equals(key)) {
return true;
}
}
return false;
}
@Override
//得到pos位置的值
public Object getPos(int pos) {
//判断是否为空
if(isEmpty()) {
return false;
}
//判断pos的合法性
if(pos < 0 || pos >= usedSize) {
return null;
}
return this.elem[pos];
}
@Override
//删除第一次出现的关键字key,不是删除key位置上的元素
public Object remove(Object key) {
//获取值为key的索引
int index = search(key);
if(index == -1) {
return null;
}
for(int i = index+1; i <= this.usedSize-1; i++) {
this.elem[i-1] = this.elem[i];
}
this.elem[usedSize-1] = null;
this.usedSize--;
return true;
}
@Override
public int size() {
return this.usedSize;
}
@Override
public void display() {
for(int i = 0; i < this.usedSize; i++) {
System.out.print(this.elem[i]+" ");
}
}
@Override
public void clear() {
for (int i = 0; i < this.usedSize; i++) {
this.elem[i] = null;
}
}
}
package com.xxx;
public class TestSequence {
public static void main(String[] args) {
ISequence sequence = new MySequenceImpl();
for(int i = 0; i < 20; i++) {
sequence.add(i, i);
}
sequence.display();
System.out.println();
System.out.println(sequence.search(19));
System.out.println(sequence.contains(18));
System.out.println(sequence.getPos(3));
System.out.println(sequence.size());
sequence.remove(5);
sequence.display();
sequence.clear();
System.out.println();
sequence.display();
}
}
用数组来存储线性表,其缺点显而易见:
1、在插入、删除元素时需要移动大量数据
2、表的大小在定义数组时便已经被指定好,虽说可以动态扩容,但是比较麻烦
静态数据表:适用于确定知道要存多少数据的场景(定长数组空间开多了浪费,开少了又不够用)
动态数据表:需要根据需要动态的分配大小
但是线性表也有它的优点,最大的优点就是可以根据下标访问数据元素。
为了克服以上缺点,就诞生了线性表的链式存储结构~
不带头不循环单向链表
带头循环单向链表
不带头双向链表