(一)线性表的定义与实现

84 阅读4分钟

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。

Java实现

/**

  • 线性表的顺序存储实现 */ 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 l = new LNode(); 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 { private static final int MAX_SIZE = 100; private static final int ERROR = -1; private int[] _data;

    public int[] get_data() { return _data; }

    private int _last;

    public int get_last() { return _last; }

    /**

    • 初始化 */ public LNode() { _data = new int[MAX_SIZE]; _last = -1; }

    /**

    • 根据指定位序返回线性表中相应元素
    • @param i 元素位序从1开始 */ public int findKth(int i) { if (i < 1 || i > this._last + 1) { System.out.println(String.format("位序%d不存在", i)); return ERROR; } return this._data[i - 1]; }

    /**

    • 在线性表查找元素

    • @param x 需要查找的元素 */ public int find(int 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, int 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表示。链接域是线性表数据单元的结构指针,用一带箭头的线段表示

,线性表的顺序是用各结点上指针构成的指针链实现的。

图片.png

为了访问链表,必须先找到链表的第一个数据单元,$\color{red}{因此实际应用中常用一个称为“表头(Header)”的指针指向链表的第一个单元,

并用他表示一个具体的链表}$。

3.1 插入

设我们要在线性表的位序为i(从1开始)的位置插入一个元素值为x。

由于链表的特性是由一个指针链来管理元素间的逻辑关系,那么当我们在线性表新增一个元素时我们需要知道这个元素的

前一个元素称为Prev和后一个元素称为Next就可以轻松在他们之间插入一个元素了。

如下图所示:

图片.png

注意这里有一种特殊情况当我们插入的位序位置为1时,将新元素的Next设置为头指针,再将头指针指向新元素即可(要确保头指针始终指向链表的第一个节点位置)

3.2 删除

设我们要删除线性表的位序为i(从1开始)的元素值,此时我们只需要找到位序为i-1的元素Prev,修改指向关系为i+1的元素即可。

如下图所示:

图片.png

注意这里有一种特殊情况当我们删除的位序位置为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 l = new PNodeList(); l.insert(1, 11); l.insert(2, 12); l.insert(3, 13); l.insert(4, 14); l.insert(5, 15); l.insert(18, 16);

     PNode tmp = l.get_head();
     while (tmp != null) {
         System.out.print(String.format("%d,", 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(32));
    
    
     l.delete(1);
     l.delete(3);
     System.out.println(l.getLength());
    
     tmp = l.get_head();
     while (tmp != null) {
         System.out.print(String.format("%d,", tmp.get_data()));
         tmp = tmp.get_next();
     }
     System.out.println();
    

    } }

class PNodeList { private static final int ERROR = -1; private PNode _head;

public PNode get_head() {
    return _head;
}

public PNodeList() {
    _head = null;
}

/**
 * 查找指定位序元素
 *
 * @param k 位序从1开始
 */
public int findKth(int k) {
    PNode 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 ERROR;
}

/**
 * 查找指定元素
 *
 * @param x 需要查找的元素
 */
public PNode find(int x) {
    PNode 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, int x) {
    PNode newNode;
    PNode 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 {

    private int _data; private PNode _next;

    public int get_data() { return _data; }

    public PNode get_next() { return _next; }

    public void set_data(int _data) { this._data = _data; }

    public void set_next(PNode _next) { this._next = _next; } }

4. 链式存储和顺序存储比较

1、顺序存储实现简单,对元素查找速度快,单对于增删来说效率较低且表长固定,适合静态管理的数据。

2、链式存储实现,增删元素效率很高,查找效率偏低,适合频繁增删节点,且表长有较大变化的情况。