1 定义
线性表是由同一类型数据元素构成的有序序列的线性结构。
线性表中元素的个数被称为线性表的长度;
当一个线性表中没有元素时,称为空表;
表的起始位置称为表头,结束位置称为表尾;
线性表的抽象数据类型描述为:
类型名称:线性表(List)
数据对象集:线性表是n(n ≥ 0)个元素构成的有序序列(a1,a2,…,an),其中a1是表的第一个元素,表头。an是表的最后一个元素,表尾。
操作集:对于一个具体的线性表 L ∈ List,一个表示位序的整数i,一个元素X∈ElementType,线性表的基本操作有:
(1) List MakeEmpty():初始化一个新的空线性表;
(2) ElementType FindKth(List L, int i):根据指定位序i返回L中相应元素ai
(3) Position Find(List L, ElementType X):已知X,返回线性表L中与X元素相同的第一个元素的位置;若不存在返回错误信息;
(4) bool Insert(List L, ElementType X, int i):在L的指定位序i前插入一个新元素X;成功则返回true,否则返回false;
(5) bool Delete(List L, int i):从L中删除指定位序i的元素;成功返回true,失败返回false;
(6) int Length(List L):返回线性表L的长度。
2 线性表的顺序存储实现
考虑到线性表的运算有插人、删除等,即表的长度是动态可变的,因此,数组的容量需设计得足够大。
假设用Data[ MAXSIZE]来表示,其中MAXSIZE是一个根据实际问题定义的足够大的整数,
线性表中的数据从Data[0]开始依次顺序存放。由于当前线性表中的实际元素个数可能未达到MAXSIZE多个,
因此需用一个变量Last记录当前线性表中最后一个元素在数组中的位置,即Last 起一个指针(实际是数组下标)的作用
,始终指向线性表中最后一个元素。表空时Last=-1。
存储结构如下图所示:
2.1 线性表的插入
设我们要插入的位序为i(从1开始),插入的元素值为x,此时我们需要将ai和an整体
后移一位为新元素腾出位置,再将x写入i-1位置,最后修改Last指向线性表最后一个元素即可。
2.2 线性表的删除
设我们要删除的位序为i(从1开始),此时我们需要将ai+1和an整体
前移一位,再将Last指向最后一个元素即可。
2.3 代码实现
Java实现
import java.lang.reflect.Array;
/**
* 线性表的顺序存储实现
*/
public class LinList01 {
public static void main(String[] args) {
System.out.println("LinList01 -> staring...");
Test();
System.out.println("LinList01 -> end");
}
public static void Test() {
LNode<Integer> l = new LNode<Integer>(Integer.class);
l.insert(1, 11);
l.insert(2, 12);
l.insert(3, 13);
l.insert(4, 14);
l.insert(5, 15);
l.insert(18, 16);
int i = 0;
while (i <= l.get_last())
System.out.print(String.format("%d,", l.get_data()[i++]));
System.out.println();
System.out.println(l.findKth(5));
System.out.println(l.findKth(6));
System.out.println(l.find(32));
System.out.println(l.getLength());
l.delete(4);
System.out.println(l.getLength());
i = 0;
while (i <= l.get_last())
System.out.print(String.format("%d,", l.get_data()[i++]));
System.out.println();
}
}
/**
* 线性表的顺序存储实现
*/
class LNode<T> {
private static final int MAX_SIZE = 100;
private static final int ERROR = -1;
private T[] _data;
public T[] get_data() {
return _data;
}
private int _last;
public int get_last() {
return _last;
}
/**
* 初始化
*/
public LNode(Class<T> clazz) {
_data = (T[]) Array.newInstance(clazz, MAX_SIZE);
_last = -1;
}
/**
* 根据指定位序返回线性表中相应元素
*
* @param i 元素位序从1开始
*/
public T findKth(int i) {
if (i < 1 || i > this._last + 1) {
System.out.println(String.format("位序%d不存在", i));
return null;
}
return this._data[i - 1];
}
/**
* 在线性表查找元素
*
* @param x 需要查找的元素
*/
public int find(T x) {
int i = 0;
while (i <= _last && _data[i] != x)
i++;
if (i > _last)
return ERROR;
else
return i;
}
/**
* 在线性表指定位置插入元素
*
* @param i 插入位序从1开始
* @param x 插入元素
*/
public boolean insert(int i, T x) {
int j;
if (this._last == MAX_SIZE - 1) {
System.out.println("表满");
return false;
}
// 检查插入的位序是否合法
if (i < 1 || i > this._last + 2) {
System.out.println("位序不合法");
return false;
}
// 位序i及以后的元素顺序向后移
for (j = this._last; j >= i - 1; j--) {
this._data[j + 1] = this._data[j];
}
// 新元素插入第i位序
this._data[i - 1] = x;
// last仍指向最后元素
this._last++;
return true;
}
/**
* 删除指定位序元素
*
* @param i 删除位序从1开始
*/
public boolean delete(int i) {
int j;
// 检查位序是否越界
if (i < 1 || i > this._last + 1) {
System.out.println(String.format("位序%d不存在", i));
return false;
}
// 将位序i+1后面的元素顺序向前移,覆盖掉需要删除的位序i
for (j = i - 1; j <= this._last; j++)
this._data[j] = this._data[j + 1];
// last仍指向最后元素
this._last--;
return true;
}
/**
* 返回线性表长度
*/
public int getLength() {
return this._last + 1;
}
}
3 线性表的链式存储实现
由于顺序表的存储特点是用物理上的相邻实现了逻辑上的相邻,它要求用连续的存储单元顺序存储线性表中各元素,
因此,对顺序表插入、删除时需要通过移动数据元素来实现,影响了运行效率。
本节介绍线性表链式存储结构,它不需要用地址连续的存储单元来实现,因为它不要求
逻辑上相邻的两个数据元素物理上也相邻
\color{red}{逻辑上相邻的两个数据元素物理上也相邻}
逻辑上相邻的两个数据元素物理上也相邻,它是通过“链”建立起数据元素之间的逻辑关系
,因此对线性表的插入、删除不需要移动数据元素,只需要修改“链"。
用链表结构可以克服数组表示线性表的缺陷。
下图为单向链表的图示表示形式,它有n个数据单元,每个数据单元由数据域和链接域两部分组成。
数据域用来存放数值.图中用a1,a2,…,an表示。链接域是线性表数据单元的结构指针,用一带箭头的线段表示
,线性表的顺序是用各结点上指针构成的指针链实现的。
为了访问链表,必须先找到链表的第一个数据单元, 因此实际应用中常用一个称为“表头( H e a d e r )”的指针指向链表的第一个单元,并用他表示一个具体的链表 \color{red}{因此实际应用中常用一个称为“表头(Header)”的指针指向链表的第一个单元, 并用他表示一个具体的链表} 因此实际应用中常用一个称为“表头(Header)”的指针指向链表的第一个单元,并用他表示一个具体的链表。
3.1 插入
设我们要在线性表的位序为i(从1开始)的位置插入一个元素值为x。
由于链表的特性是由一个指针链来管理元素间的逻辑关系,那么当我们在线性表新增一个元素时我们需要知道这个元素的
前一个元素称为Prev和后一个元素称为Next就可以轻松在他们之间插入一个元素了。
如下图所示:
注意这里有一种特殊情况当我们插入的位序位置为1时,将新元素的Next设置为头指针,再将头指针指向新元素即可(要确保头指针始终指向链表的第一个节点位置)
3.2 删除
设我们要删除线性表的位序为i(从1开始)的元素值,此时我们只需要找到位序为i-1的元素Prev,修改指向关系为i+1的元素即可。
如下图所示:
注意这里有一种特殊情况当我们删除的位序位置为1时,仅需要将头指针指向位序为2的元素即可。
3.3 实现
Java实现
/**
* 线性表的链式存储实现
*/
public class LinList02 {
public static void main(String[] args) {
System.out.println("LinList02 -> staring...");
Test();
System.out.println("LinList02 -> end");
}
public static void Test() {
PNodeList<Double> l = new PNodeList<Double>();
l.insert(1, 11.12);
l.insert(2, 12.2);
l.insert(3, 13.5);
l.insert(4, 14.6);
l.insert(5, 15.12);
l.insert(18, 16.1);
PNode tmp = l.get_head();
while (tmp != null) {
System.out.print(String.format("%f,", tmp.get_data()));
tmp = tmp.get_next();
}
System.out.println();
System.out.println(l.findKth(5));
System.out.println(l.findKth(6));
System.out.println(l.find(15.12));
l.delete(1);
l.delete(3);
System.out.println(l.getLength());
tmp = l.get_head();
while (tmp != null) {
System.out.print(String.format("%f,", tmp.get_data()));
tmp = tmp.get_next();
}
System.out.println();
}
}
class PNodeList<T> {
private static final int ERROR = -1;
private PNode<T> _head;
public PNode get_head() {
return _head;
}
public PNodeList() {
_head = null;
}
/**
* 查找指定位序元素
*
* @param k 位序从1开始
*/
public T findKth(int k) {
PNode<T> p = _head;
int counter = 1;
while (p != null && counter < k) {
p = p.get_next();
counter++;
}
if (counter == k && p != null)
return p.get_data();
else
return null;
}
/**
* 查找指定元素
*
* @param x 需要查找的元素
*/
public PNode<T> find(T x) {
PNode<T> p = _head;
while (p != null && p.get_data() != x)
p = p.get_next();
return p;
}
/**
* 在指定位序插入元素
*
* @param i 位序,从1开始
* @param x 需要插入的元素
*/
public boolean insert(int i, T x) {
PNode<T> newNode;
PNode<T> prev;
// 生成新节点
newNode = new PNode();
newNode.set_data(x);
// 插入位序为表头处理
if (i == 1) {
newNode.set_next(_head);
_head = newNode;
return true;
}
// 查找插入位序前一个元素 i-1
prev = FindPrev(i);
if (prev == null) return false;
// 插入元素
newNode.set_next(prev.get_next());
prev.set_next(newNode);
return true;
}
private PNode FindPrev(int i) {
PNode prev = _head;
int counter = 1;
while (counter < i - 1 && prev != null) {
prev = prev.get_next();
counter++;
}
if (prev == null || counter != i - 1) {
System.out.println("插入位序不合法");
return null;
}
return prev;
}
/**
* 删除指定位序节点
*
* @param i 位序
*/
public boolean delete(int i) {
PNode tmp;
PNode prev;
// 删除的位序为1
if (i == 1) {
tmp = _head;
_head = tmp.get_next();
tmp.set_next(null);
return true;
}
// 查找Prev元素
// 查找插入位序前一个元素 i-1
prev = FindPrev(i);
if (prev == null) return false;
// 删除元素
tmp = prev.get_next();
prev.set_next(tmp.get_next());
tmp.set_next(null);
return true;
}
public int getLength() {
PNode p = _head;
int counter = 0;
while (p != null) {
counter++;
p = p.get_next();
}
return counter;
}
}
/**
* 线性表链式存储实现
*/
class PNode<T> {
private T _data;
private PNode _next;
public T get_data() {
return _data;
}
public PNode get_next() {
return _next;
}
public void set_data(T _data) {
this._data = _data;
}
public void set_next(PNode _next) {
this._next = _next;
}
}
4. 链式存储和顺序存储比较
1、顺序存储实现简单,对元素查找速度快,单对于增删来说效率较低且表长固定,适合静态管理的数据。
2、链式存储实现,增删元素效率很高,查找效率偏低,适合频繁增删节点,且表长有较大变化的情况。