线性表的定义
- 定义
- 线性表(List):零个或多个数据元素的有限序列。
- 线性表,顾名思义,是具有像线一样的性质的表。
线性表说明
- 首先它是一个序列,也就是说,元素之间是有顺序的
- 若元素存在多个,则第一个元素无前驱,最后一个元素无后继,其他每个元素都有且只有一个前驱和后继。
- 如果一个小朋友去拉两个小朋友后面的衣服,那就不可以排成一队了;
- 同样,一个小朋友的衣服也不能被两个甚至更多小朋友拉;
- 然后,线性表强调是有限的,小朋友班级人数是有限的,元素个数当然也是有限的。
- 事实上,在计算机中处理的对象都是有限的,那种无限的数列,只存在于数学的概念中。
- 对于非空的线性表或者线性结构的特点:
- 存在唯一的一个被称作“第一个”的数据元素;
- 存在唯一的一个被称作“最后一个”的数据元素;
- 除第一个外,结构中的每个数据元素均只有一个前驱;
- 除最后一个外,结构中的每个数据元素均只有一个后继;
- 可用数学语言来进行定义:
- 若将线性表记为(a1, … , ai-1, ai, ai+1, … , an)
- 则表中 ai-1 领先于 ai,ai+1 领先于 ai,称 ai-1 是 ai 的直接前驱元素
- ai+1 是 ai 的直接后继元素。
- 当 i = 1,2, … , n-1 时,ai 有且仅有一个直接后继,
- 当 i = 2,3, … , n 时,ai 有且仅有一个直接前驱。
- 如图:
- 线性表的合并和归并的区别:
- 合并:只是将两个线性表合并为一个线性表,并
不保证是有序的
。 - 归并:要求两个线性表合并之后,
仍然有序
。
线性表的抽象数据类型
我们知道啦线性表的定义,在我们来分析一下,线性表应该有一些什么样的操作。
幼儿园小朋友的例子,老师为了让小朋友有秩序地出入,所以就考虑给他们排一个队,并且是长期使用的顺序,这个考虑和安排的过程其实就是一个线性表的创建和初始化过程。
一开始没经验,把小朋友排好队后,发现有的高有的矮,队伍很难看,于是就让小朋友解散重新排——这是一个线性表重置为空表的操作。
排好了队,我们随时可以叫出队伍某一位置的小朋友名字及他的具体情况。比如有家长问,队伍里第五个孩子,怎么这么调皮,他叫什么名字呀,老师可以很快告诉这位家长,这就是宋远桥的儿子,叫宋青书。这种可以根据位序得到数据元素也是一种很重要的线性表操作。
还有什么呢,有时我们想知道,某个小朋友,比如郭靖是否是班里的小朋友,老师会说,不是,郭靖在桃花岛幼儿园里,不在我们幼儿园。这种查找某个元素是否存在的操作很常用。
而后有家长问老师,班里现在到底有多少个小朋友呀,这种获得线性表长度的问题也很普遍。
显然,对于一个幼儿园来说,加入一个新的小朋友到队列中,或因某个小朋友生病,需要移除某个位置,都是很正常的情况。对于一个线性表来说,插入数据和删除数据都是必须的操作。
dataType [汉:数据类型]
- 线性表的数据对象集合为{a1, a2, …… ,an},其中每个元素的类型均为DataType。
- 其中,除第一个元素a1外,每一个元素有且只有一个直接前驱元素。
- 除了最后一个元素an外,每一个元素有且只有一个直接后继元素。
- 数据元素之间的关系是一对一的关系。
Operation[汉:操作]
- InitList(*L): 初始化操作,建立一个空的线性表L。
- ListEmpty(L): 若线性表为空,返回true,否则返回false。
- ClearList(*L): 将线性表清空。
- GetElem(L, i, *e): 将线性表L中的第 i 个位置元素值返回给e。
- LocateElme(L, e): 在线性表L中查找与给定值e相等的元素,
- 如果查找成功,返回该元素在表中序号表示成功;
手动实现union操作[伪代码]
/* 将所有的在线性表 Lb 中但不在 La 中的数据元素插入到 La 中 */ void unionL(List *La, List Lb) { int La_len, Lb_len, i; /* 声明与 La 和 Lb 相同的元素 e */ ElemType e ; /* 求线性表的长度 */ La_len = ListLength(*La); Lb_len = ListLength(Lb); for (i = 1; i <= Lb_len; i++) { /* 取 Lb 中第 i 个数据元素赋给 e */ GetElem(Lb, i, &e); /* La 中不存在和 e 相同的数据元素 */ if (!LocateElem(*La, e)) /* 插入 */ ListInsert(La, ++La_len, e); } }
线性表其 顺序存储结构
顺序表的定义
所谓顺序表,就是顺序存储的线性表。顺序存储是用一组地址连续的存储单元依次存放线性表中的各个数据元素的存储结构
顺序表的特点
顺序存储
- 线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素,是线性表的两种物理结构的一种。
- 线性表(a1,a2,…,an)的顺序存储示意图如下:
举例
现实中也有这样的例子,在大学,一个宿舍八个人,小雨人特别老实、热心,我们时常会让他帮我们去图书馆占座,他总是答应,你想想,这其实明摆着是欺负人的事。他每次一吃完早饭就冲去图书馆,挑一个好地儿,把他书包里的书,一本一本地按座位放好,若书包里的书不够,他会把他的饭盒、水杯、水笔都用上,长长一排,九个座硬是被他占了。小雨同学占座时,如果图书馆里空座很多,他当然不必一定要选择第一排第一个位子,而是可以选择风水不错、美女较多的地儿。找到后,放一个书包在第一个位置,就表示从这开始,这地方暂时归我了。
接着,因为一共八个人,所以他需要占八个座。线性表中,我们估算这个线性表的最大存储容量,建立一个数组,数组的长度就是这个最大存储容量。
可实际上,一个宿舍总有那么几个不是很好学的人,为了游戏,为了恋爱,就不去图书馆自习了。假设我们八个人,去了六个,真正被使用的座位也就只是六个,另两个是空的。同样的,我们已经有了起始的位置,也有了最大的容量,于是我们可以在里面增加数据了。随着数据的插入,我们线性表的长度开始变大,不过线性表的当前长度不能超过存储容量,即数组的长度。想想也是,如果我们有九个人,只占了八个座,自然是坐不下的。
专业说明
- 线性表的顺序存储结构,说白了,就是在内存中找了块地儿,通过占位的形式,把一定内存空间给占了,然后把相同数据类型的数据元素依次存放在这块空地中。
- 既然线性表的每个数据元素的类型都相同,所以可以用C|java语言(其他语言也相同)的一维数组来实现顺序存储结构,即把第一个数据元素存到数组下标为0的位置中,接着把线性表相邻的元素存储在数组中相邻的位置。
- 为了建立一个线性表,要在内存中找一块地,于是这块地的第一个位置就非常关键,它是存储空间的起始位置。
顺序存储结构需要三个属性:
- 存储空间的起始位置:数组data,它的存储位置就是存储空间的存储位置。
- 线性表的最大存储容量:数组长度MaxSize。
- 线性表的当前长度:length.
private int MaxSize = 10; private int [] seqYaHui = new int[MaxSize];
数组的长度 和 线性表的长度区分
- 数组的长度是存放线性表的存储空间的长度,存储分配后这个量一般是不变的。
- 可以动态分配的一维数组。是的,一般高级语言,比如C、VB、C++都可以用编程手段实现动态分配数组,不过这会带来性能上的损耗。
- 线性表的长度是线性表中数据元素的个数,随着线性表插入和删除操作的进行,这个量是变化的。
- 在任意时刻,线性表的长度应该小于等于数组的长度。
顺序表的Java代码实现
线性表抽象数据类型的Java接口描述
package linearTable; /** * 线性表抽象数据类型的Java接口描述 */ public interface IList { // 将一个已经已经存在的线性表置为空表 public void clear(); // 判断线性表是否为空 public boolean isEmpty(); // 求线性表中数据元素的个数并且返回 public int length(); // 读取并返回线性表中的第i个数据元素 public Object get(int i); // 在线性表第i个数据元素之前插入一个值为o的数据元素. public void insert(int i, Object o); // 删除线性表中下标(位序号)中第i个数据元素 public void remove(int i); // 返回线性表中首次出现o的下标(位序号) public int indexOf(Object o); // 输出线性表中所有的数据 public void display(); }
线性表类的实现
package linearTable; /** * 顺序线性表及其基本操作 */ public class SqList implements IList { private final Object[] listElem; // 线性表存储空间 private int curLen; // 当前长度 // 顺序表的构造函数,构造一个存储空间容量为maxSize的线性表 public SqList(int maxSize) { curLen = 0; // 置顺序表的当前长度为0 listElem = new Object[maxSize];// 为顺序表分配maxSize个存储单元 } // 将一个已经存在的线性表置成空表 public void clear() { curLen = 0; // 置顺序表的当前长度为0 } // 判断当前线性表中数据元素个数是否为0,若为0则函数返回true,否则返回false public boolean isEmpty() { return curLen == 0; } // 求线性表中的数据元素个数并由函数返回其值 public int length() { return curLen; // 返回顺序表的当前长度 } // 读取到线性表中的第i个数据元素并由函数返回其值。其中i取值范围为:0≤i≤length()-1,如果i值不在此范围则抛出异常 public Object get(int i) throws Exception { if (i < 0 || i > curLen - 1) // i小于0或者大于表长减1 throw new Exception("第" + i + "个元素不存在"); // 输出异常 return listElem[i]; // 返回顺序表中第i个数据元素 } // 在线性表的第i个数据元素之前插入一个值为x的数据元素。其中i取值范围为:0≤i≤length()。如果i值不在此范围则抛出异常,当i=0时表示在表头插入一个数据元素x,当i=length()时表示在表尾插入一个数据元素x public void insert(int i, Object x) throws Exception { if (curLen == listElem.length) // 判断顺序表是否已满 throw new Exception("顺序表已满");// 输出异常 if (i < 0 || i > curLen) // i小于0或者大于表长 throw new Exception("插入位置不合理");// 输出异常 for (int j = curLen; j > i; j--) listElem[j] = listElem[j - 1];// 插入位置及之后的元素后移 listElem[i] = x; // 插入x curLen++;// 表长度增1 } // 将线性表中第i个数据元素删除。其中i取值范围为:0≤i≤length()- 1,如果i值不在此范围则抛出异常 public void remove(int i) throws Exception { if (i < 0 || i > curLen - 1) // i小于1或者大于表长减1 throw new Exception("删除位置不合理");// 输出异常 for (int j = i; j < curLen - 1; j++) listElem[j] = listElem[j + 1];// 被删除元素之后的元素左移 curLen--; // 表长度减1 } // 返回线性表中首次出现指定元素的索引,如果列表不包含此元素,则返回 -1 public int indexOf(Object x) { int j = 0;// j为计数器 while (j < curLen && !listElem[j].equals(x)) // 从顺序表中的首结点开始查找,直到listElem[j]指向元素x或到达顺序表的表尾 j++; if (j < curLen)// 判断j的位置是否位于表中 return j; // 返回x元素在顺序表中的位置 else return -1;// x元素不在顺序表中 } // 输出线性表中的数据元素 public void display() { for (int j = 0; j < curLen; j++) System.out.print(listElem[j] + " "); System.out.println();// 换行 } // 实现对顺序表就地逆置 public void reverse() { for (int i = 0, j = curLen - 1; i < j; i++, j--) { Object temp = listElem[i]; listElem[i] = listElem[j]; listElem[j] = temp; } } }
线性表顺序结构的优缺点