第一节:线性表的顺序存储结构
1、线性结构和非线性结构
线性结构
线性结构特点:在数据元素的非空有限集中
- 存在唯一的一个被称作“第一个”的数据元素
- 存在唯一的一个被称作“最后一个”的数据元素
- 除第一个元素外,集合中的每个数据元素均只有一个前驱
- 除最后一个元素外,集合中的每个数据元素均只有一个后继
线性结构作为最常用的数据结构,其特点是内部每个数据元素之间存在一对一的线性关系,即每个元素都只有一个前驱或者一个后继或一个前驱和后继
- 线性结构有两种不同的存储结构,即顺序存储结构(比如数组)和 链式存储结构(链表),顺序存储的线性表为顺序表,顺序表中存储的元素的内存地址是连续的
- 链式存储结构的线性表称为链表,链表中的存储的元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息,
- 线性结构常见的有:数组,队列,链表,栈
非线性结构
相对应于线性结构,非线性结构的逻辑特征是一个结点元素可能对应多个直接前驱和多个后继,存在这种一对多的关系,比如树结构,就不存在一对一的关系了。非线性结构包括:二维数组,多维数组,广义表,树结构,图结构
2、线性表的类型和定义
摘自大话数据结构:这种类似于孩子按照顺序一个接一个排好队的组织方式,其实就是今天我们要介绍的数据结构: 线性表。
线性表是最常用且最简单的一种数据结构。简言之,一个线性表是0个或多个数据元素的有限序列
- 线性表在物理结构上来说肯定是线性结构,首先它是一个序列。也就是说,元素之间是有顺序的,若元素存在多个,则第一 个元素无前驱, 最后一个元素无后继,其他每个元素都有且只有一个前驱和后继。
- 内部数据元素个数是有限的,n=0时,线性表成为空表
至于每个数据元素的具体含义,在不同的情况下各不相同,它可以是一个数或一个符号,也可以是一页书,甚至其他更复杂的信息。例如,26个英文字母的字母表:(A,B,C,D,……,Z)是一个线性表,表中的数据元素是单个字母字符。
在稍复杂的线性表中,一个数据元素可以由若干个数据项组成。在这种情况下,常把数据元素称为记录,含有大量记录的线性表又称文件。例如,一个学校的学生学籍登记表,表中每个学生的情况为一个记录,它有姓名、学号、性别、出生日期、政治面貌等5个数据项组成。
综上,线性表中的数据元素可以是各种各样的,但同一线性表中的元素必定具有相同特性,即属于相同类型,相邻数据元素之间存在着序偶关系
线性表是一个相当灵活的数据结构,它的长度可根据需要增长或缩短,即对线性表的数据元素不仅可以进行访问,还可进行插入和删除等。
3、线性表的顺序存储结构
3.1、顺序存储结构概述
顺序存储的线性表为顺序表,线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。
线性表(a1,a2,a3,....an)顺序存储示意图如下:
因为顺序存储结构是用一段连续地址来存储数据类型相同的数据元素,所以可以借助一维数组来实现顺序存储结构,即把第一个数据元素存到下标为0的位置中,把线性表相邻元素存储在数组中相邻位置
而在内存中存储这一个线性表的第一个位置就是存储空间的起始位置,线性表的最大容量就是我们要为之分配内存空间的存储容量,考虑用数组实现,则数组的长度就是这个最大容量,随着数据的插入,线性表的长度开始变大,不过线性表的当前长度不能超过存储容量,即数组的长度
线性表顺序存储结构代码(Java演示):
private final int MAXSIZE = 20;//存储空间初始分配量
private int length; //线性表当前长度
private Object[] elementData;//定义一个数组,存储线性表的数据元素,最大可以分配的数组长度为MAXSIZE
这里,我们就发现描述顺序在储结构需要三个属性:
- 存储空间的起始位置:数组elementData,它的存储位置就是存储空间的存储位置。
- 线性表的最大存储容量: 数组长度MaxSize。
- 线性表的当前长度: length。
1、数据长度和线性表长度的区别:
- 数组长度是存放线性表的存储空间长度,存储分配后这个量一般是不变的
- 线性表长度是线性表中元素的个数,随着线性表插入和删除操作的进行,这个量是变化的
- 需要注意的是:在任何时刻,线性表的长度应该小于等于数组的长度。
2、地址计算方法
【逻辑结构到存储结构的映射】 数组从0开始第一个下标,于是线性表的第i个元素是要存储在数组下标为i-1的位置,即数据元素的序号和存放它的数组下标之间存在对应关系(如下图所示)。
用数组存储顺序表意味着要分配固定长度的数组空间,由于线性表中可以进行插入和删除操作,因此分配的数组空间要大于等于当前线性表的长度。
存储器中的每个存储单元都有自己的编号,这个编号称为地址。假设线性表一共占用的是C个存储单元:那么线性表中第i+1个数据元素的存储位置和第i个数据元素的存储位置满足下列关系(LOC表示获得存储位置的函数)。
LOC(a(i+1)) = LOC(a(i))+c
LOC(a(i)) = LOC(a(1)) + (i-1) * c ;i=1,2,3...n
图示:
通过这个公式,你可以随时算出线性表中任意位置的地址,不管它是第一个还是最后一个,都是相同的时间。
3.2、顺序表创建
利用一维数组实现,案列如下:创建如下顺序表,并进行增删改查操作
| 索引(下标) | 0 | 1 | 2 | 3 | 4 | ............ |
|---|---|---|---|---|---|---|
| 数据元素 | 1 | 2 | 3 | 4 | 5 | ............ |
public class SequenceList{
private final int MAXSIZE = 10;//存储空间初始分配量
private int capacity; //线性表当前长度
private int[] elementData;//定义一个数组,存储线性表的数据元素,最大可以分配的数组长度为MAXSIZE
//构造器方式以默认最大长度创建顺序表,并且里面分配1-2-3-4-5 5个数据元素
public SequenceList(){
this.elementData = new int[MAXSIZE];//分配数组长度
this.capacity = 0;//空表容量为0
//向空的顺序线性表中添加元素1-2-3-4-5
for (int i = 0; i < 5; i++) {
elementData[i] = i+1;
capacity++;
System.out.print(elementData[i]+"-");
}
System.out.println();
}
3.3、获取指定位置元素
实现思路:
- 首先,判断需要获得元素的下标位置是否合理。合理即可将线性表L中的第i个位置元素值返回,如果元素不存在则抛出异常
代码实现:
//获取指定位置i处的元素,i从0开始
public int getElement(int i) throws Exception{
//为空表或者索引错误则抛出异常
if(capacity == 0|| i<0 || i> capacity){
throw new Exception("顺序表为空或索引不符合规范");
}
return elementData[i];
}
3.4、根据元素值查找它第一次出现的下标
public int getIndex(int element) throws Exception{
//空表
if(capacity == 0){
throw new Exception("顺序表为空");
}
int index = -1; //不存在该元素则返回-1
for (int i = 0; i < capacity; i++){
if (elementData[i] == element){
index = i;
break;//找到第一个就退出循环
}
}
return index;
}
3.5、插入元素操作
插入算法的思路:
- 如果插入位置不合理,抛出异常;
- 如果线性表长度大于等于数组长度,则抛出异常或动态增加容量;这里演示的是直接抛出异常
- 从最后一个元素开始向前遍历到第i个位置,分别将它们都向后移动一个位置;
- 将要插入元素填入位置i处;
- 线性表长加1。
/**
* 向顺序线性表中的指定位置插入元素
* @param element 要插入的元素
* @param index 插入元素的位置
*/
public void insert(int element,int index) throws Exception{
if(capacity == MAXSIZE){
throw new Exception("线性表已满,不能插入");
}
//index可以等于capacity,即插入在最后一个元素后面
if (index < 0 || index > capacity){
throw new Exception("插入元素位置超过线性表范围");
}
//符合要求的位置:
//下标处在数据已有元素之间或下标等于capacity,即末尾元素后面一个元素的下标
//从最后一个元素开始向前遍历到第i个位置,分别将它们都向后移动一个位置;
for(int j = capacity-1; j >= index; j--) {
elementData[j + 1] = elementData[j];
}
//将要插入元素填入位置i处;如果是插入在最后一个元素后面,即index = capacity,则直接添加
elementData[index] = element;
//插入一个元素,当前线性表长度+1
capacity++;
}
3.6、删除操作
删除算法的思路:
- 如果删除位置不合理,抛出异常;
- 取出删除元素;
- 从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置;
- 表长减1。
/**
* 删除指定位置元素
* @param index 输入要删除的元素位置
*/
public void delete(int element,int index) throws Exception {
if(capacity == 0){
throw new Exception("线性表为空,不能插入");
}
if (index < 0 || index > capacity){
throw new Exception("插入元素位置超过线性表范围");
}
//取出删除元素;
element = elementData[index];
//从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置;
for(int j = index; j < capacity; j++){
elementData[j] = elementData[j+1];
}
capacity--;
}
3.7、总体代码
public class SequenceList{
private final int MAXSIZE = 10;//存储空间初始分配量
private int capacity; //线性表当前长度
private int[] elementData;//定义一个数组,存储线性表的数据元素,最大可以分配的数组长度为MAXSIZE
//构造器方式以默认最大长度创建顺序表,并且里面分配1-2-3-4-5 5个数据元素
public SequenceList(){
this.elementData = new int[MAXSIZE];//分配数组长度
this.capacity = 0;//空表容量为0
//向空的顺序线性表中添加元素1-2-3-4-5
for (int i = 0; i < 5; i++) {
elementData[i] = i+1;
capacity++;
System.out.print(elementData[i]+"-");
}
System.out.println();
}
//获取指定位置i处的元素,i从0开始
public int getElement(int i) throws Exception{
//为空表或者索引错误则抛出异常
if(capacity == 0|| i<0 || i> capacity){
throw new Exception("顺序表为空或索引不符合规范");
}
return elementData[i];
}
//根据元素值查找它第一次出现的下标
public int getIndex(int element) throws Exception{
//空表
if(capacity == 0){
throw new Exception("顺序表为空");
}
int index = -1; //不存在该元素则返回-1
for (int i = 0; i < capacity; i++){
if (elementData[i] == element){
index = i;
break;//找到第一个就退出循环
}
}
return index;
}
/**
* 向顺序线性表中的指定位置插入元素
* @param element 要插入的元素
* @param index 插入元素的位置
*/
public void insert(int element,int index) throws Exception{
if(capacity == MAXSIZE){
throw new Exception("线性表已满,不能插入");
}
if (index < 0 || index > capacity){
throw new Exception("插入元素位置超过线性表范围");
}
for(int j = capacity-1; j >= index; j--) {
elementData[j + 1] = elementData[j];
}
elementData[index] = element;
capacity++;
}
/**
* 删除指定位置元素
* @param index 输入要删除的元素位置
*/
public void delete(int element,int index) throws Exception {
if(capacity == 0){
throw new Exception("线性表为空,不能插入");
}
if (index < 0 || index > capacity){
throw new Exception("插入元素位置超过线性表范围");
}
element = elementData[index];
for(int j = index; j < capacity; j++){
elementData[j] = elementData[j+1];
}
capacity--;
}
//遍历输出顺序表
public void getdata() {
for (int i = 0; i < capacity; i++) {
System.out.print(elementData[i] + " ");
}
System.out.println();
}
}
3.8、测试
正确情况测试
public static void main(String[] args) throws Exception {
//初始化线性表
System.out.println("创建好线性表以后里面的元素为:");
SequenceList seqlist = new SequenceList();
//获取位置1处的元素2
System.out.println("下标为1处的元素是:" + seqlist.getElement(1));
//向当前顺序线性表中下标为2的位置插入元素100
seqlist.insert(100,2);
System.out.println("下标为2的位置插入100后的线性表为:");
seqlist.getdata();
//向当前顺序线性表中末尾的位置插入元素200
seqlist.insert(200,6);
System.out.println("末尾的位置插入200后的线性表为:");
seqlist.getdata();
//删除下标为1的元素2
seqlist.delete(2,1);
System.out.println("删除下标为1的元素2后的线性表为:");
seqlist.getdata();
}
结果:
创建好线性表以后里面的元素为:
1-2-3-4-5-
下标为1处的元素是:2
下标为2的位置插入100后的线性表为:
1 2 100 3 4 5
末尾的位置插入200后的线性表为:
1 2 100 3 4 5 200
删除下标为1的元素2后的线性表为:
1 100 3 4 5 200
异常情况测试
public static void main(String[] args) throws Exception {
//初始化线性表
System.out.println("创建好线性表以后里面的元素为:");
SequenceList seqlist = new SequenceList();
//向当前顺序线性表中下标为7的位置插入元素300
seqlist.insert(300,7);
System.out.println("下标为7的位置插入300后的线性表为:");
seqlist.getdata();
}
结果:
创建好线性表以后里面的元素为:
1-2-3-4-5-
Exception in thread "main" java.lang.Exception: 插入元素位置超过线性表范围
at SequenceList.insert(SequenceList.java:64)
at SequenceList.main(SequenceList.java:117)
元素查找测试
public static void main(String[] args) throws Exception {
//初始化线性表
System.out.println("创建好线性表以后里面的元素为:");
SequenceList seqlist = new SequenceList();
//当前顺序线性表中末尾的位置插入元素200
seqlist.insert(200,5);
System.out.println("末尾的位置插入200后的线性表为:");
seqlist.getdata();
//向当前顺序线性表中末尾的位置继续插入元素200
seqlist.insert(200,6);
System.out.println("末尾的位置继续插入200后的线性表为:");
seqlist.getdata();
int m = seqlist.getIndex(200);
System.out.println("200第一次出现的下标为:" + m);
}
结果:
创建好线性表以后里面的元素为:
1-2-3-4-5-
末尾的位置插入200后的线性表为:
1 2 3 4 5 200
末尾的位置继续插入200后的线性表为:
1 2 3 4 5 200 200
200第一次出现的下标为:5
4、插入和删除的时间复杂度分析
最好的情况
如果元素要插入到最后一个位置,或者删除最后一个元素,此时时间复杂度为O(1), 因为不需要移动元素的,就如同来了一个新人要正常排队,当然是排在最后,如果此时他又不想排了,那么他一个人离开就好了,不影响任何人。
最坏的情况
如果元素要插入到第一个位置或者删除第一个元素,此时时间复杂度是多少呢?那就意味着要移动所有的元素向后或者向前,所以这个时间复杂度为O(n)。
平均的情况
由于元素插入到第i个位置,或删除第i个元素,需要移动n-i个元素。根据概率原理,每个位置插入或删除元素的可能性是相同的,也就说位置靠前,移动元素多,位置靠后,移动元素少。最终平均移动次数和最中间的那个元素的移动次数相等,为n-1
我们前面讨论过时间复杂度的推导,可以得出,平均时间复杂度还是O(n)。这说明什么?线性表的顺序存储结构,在存、读数据时,不管是哪个位置,时间复杂度都是O(1);而插入或删除时,时间复杂度都是O(n)。这就说明,它比较适合元素个数不太变化,而更多是存取数据的应用。当然,它的优缺点还不只这些....