Skip to content

Latest commit

 

History

History
285 lines (219 loc) · 7.78 KB

2.线性表.md

File metadata and controls

285 lines (219 loc) · 7.78 KB

2.1 线性表的定义和基本操作

2.1.1 线性表的定义

线性表是具有相同数据类型的n个数据元素的有限序列。一般表示为:L=(a1, a2, ..., an)

  1. 除了第一个元素外,每个元素有且仅有一个前驱;第一个元素只有后继,没有前驱。
  2. 除最后一个元素外,每个元素有且仅有一个后继;最后一个元素只有前驱,没有后继。

2.1.2 线性表的基本操作

  1. InitList(&L): 初始化表。构造一个空的线性表
  2. Length(L): 求表长。返回线性表L的长度,即L中数据元素的个数
  3. LocateElem(L, e): 按值查找操作
  4. GetElem(L, i): 按位查找
  5. ListInsert(&L, i, e): 插入操作
  6. ListDelete(&L,i, &e): 删除操作
  7. PrintList(L): 输出操作
  8. Empty(L): 判空操作
  9. DestroyList(&L): 销毁操作

2.2 线性表的顺序表示

2.2.1 顺序表的定义

线性表的顺序存储又称为顺序表。存储类型描述为:

#define MaxSize 50              // 定义线性表的最大长度
typedef struct {
    ElemType data[MaxSize];     // 顺序表的元素
    int length;                 // 顺序表当前长度
} SqList;                       // 顺序表的类型定义

一维数组可以是静态分配的,也可以是动态分配的。静态分配时,由于数组的大小和空间是事先已经固定的,所以一旦空间占满,再加入新的数据会导致溢出。动态分配时若数据空间占满,可以另外开辟一个更大的存储空间替换原有存储空间。

#define InitSize 100            // 表长度的初始定义
typedef struct {
    ElemType *data;             // 指示动态分配数组的指针
    int MaxSize, length;        // 数组的最大容量和当前个数
} SeqList;                      // 动态分配数组顺序表的类型定义

C的初始态分配语句为:

L.data = (ElemType *)malloc(sizeof(ElemType) * InitSize);

C++的初始态分配语句为:

L.data = new ElemType[InitSize];

特点:

  1. 随机访问,即通过首地址和元素序号可以在O(1)的时间内找到指定的元素。
  2. 存储密度高,每个结点只存储数据元素。
  3. 逻辑上相邻的数据元素在物理上也相邻,所以插入删除操作需要移动大量元素。

2.2.2 顺序表上基本操作的实现

1.插入操作

时间复杂度:O(n)

bool ListInsert(SqList &L, int i, ElemType e) {
    // 将元素e插入到顺序表L的第i个位置
    if (i < 1 || L > L.length + 1) {
        return false;
    }
    if (L.length >= MaxSize) {
        return false;
    }
    for (int j = L.length; j >= i; j--) {
        L.data[j] = L.data[j - 1];
    }
    L.data[i - 1] = e;
    L.length++;
    return true;
}

2.删除操作

时间复杂度:O(n)

bool ListDelete (SqList &L, int i, ElemType &e) {
    // 删除顺序表L中第i个元素,并将删除的值用引用变量e返回
    if (i < 1 || i > L.length) {
        return false;
    }
    e = L.data[i - 1];
    for (int j = i; j < L.length; j++) {
        L.data[j - 1] = L.data[j];
    }
    L.length--;
    return true;
}

3.查找操作

时间复杂度:O(n)

int LocateElem (SqList L, ElemType e) {
    // 查找顺序表中值为e的元素,并返回元素位序,否则0
    int i = 0;
    for (i = 0; i < L.length; i++) {
        if (L.data[i] == e) {
            return i + 1;
        }
    }
    return 0;
}

2.3 线性表的链式表示

2.3.1 单链表的定义

线性表的链式存储又称为单链表,它是指通过一组任意的存储单元来存储线性表中的数据元素。每个链表结点,除了自身信息外还需要存放一个指向其后继的指针。

typedef struct LNode {      // 定义单链表结点类型
    ElemType data;          // 数据域
    struct LNode *next;     // 指针域
} LNode, *LinkList;

单链表是非随机存取的存储结构,即不能直接查找到表中某个特定的结点。

通常使用头指针来标识一个单链表,若为NULL,则表示空链表。有时附加一个结点作为头结点,头结点指向的下一个结点才是数据结点,若指向为NULL,则带头结点的单链表为空。引入头结点的优点:

  1. 由于开始结点的位置存放在头结点的指针域中,所以在链表的第一个位置上的操作和表中其他位置上的操作一致。
  2. 无论链表是否为空,其头指针是指向头结点的非空指针,因此空表和非空表统一处理。

2.3.2 单链表上基本操作的实现

1. 采用头插法建立单链表

生成的新结点插入到当前链表的表头,所以读入顺序与表顺序是相反的,插入结点时间复杂度O(1),长度为n的单链表创建时间复杂度为O(n):

LinkList CreateList1 (LinkList &L) {
    // 从表尾到表头建立单链表L,每次均在头结点之后插入元素
    LNode *s;
    int x;
    L = (LinkList)malloc(sizeof(LNode));
    L->next = NULL;
    scanf("%d", &x);
    whild (x != 999) {
        s = (LNode *)malloc(sizeof(LNode));
        s->data = x;
        s->next = L->next;
        L->next = s;
        scanf("%d", &x);
    }
    return L;
}

2. 采用尾插法建立单链表

由于头插法的逆序问题,所以可以通过增加一个尾指针r来实现尾插法,时间复杂如与头插法相同:

LinkList CreateList2 (LinkList &L) {
    // 正向创建单链表
    int x;
    L = (LinkList)malloc(sizeof(LNode));
    LNode *s, *r = L;
    scanf("%d", &x);
    while (x != 999) {
        s = (LNode *)malloc(sizeof(LNode));
        s->data = x;
        // s->next = NULL;
        r->next = s;
        r = s;
    }
    r->next = NULL;
    return L;
}

3. 按序号查找结点值

时间复杂度为O(n):

LNode *GetElem (LinkList L, int i) {
    // 获取带头结点的单链表第i个位置的结点指针
    int j = 1;
    LNode *p = L->next;
    if (i == 0) {
        return L;
    }
    if (i < 1) {
        return NULL;
    }
    while (p && j < i) {
        p = p->next;
        j++;
    }
    return p;
}

4. 按值查找表结点

时间复杂度O(n):

LNode *LocateElem (LinkList, L, ElemType e) {
    // 返回值域为e的结点指针
    LNode *p = L->next;
    while (p != NULL && p->data != e) {
        p = p->next;
    }
    return p;
}

5. 插入结点操作

将值为x的新结点s插入到指定位置i的单链表中,也就是在第i-1个位置后插入新的结点。查找时间复杂度为O(n),插入时间复杂度为O(1):

  1. p = GetElem(L, i - 1);
  2. s->next = p->next;
  3. p->next = s;

上述方法属于前插法,还有一种为后插法,即在第i个位置后插入新的结点,然后将第i位置和第i+1位置结点中的值进行交换,但需要额外的中间变量。

6. 删除结点操作

找到第i-1个位置的结点指针,然后删除其后一个位置的结点即为删除第i个位置的结点,时间复杂度为O(n)。

7. 求表长操作

遍历单链表,计算不含头结点在内的结点数量。

2.3.3 双链表

单个结点同时存在两个指针,一个指向前驱,一个指向后继:

typedef struct DNode {
    ElemType data;
    struct DNode *prior, *next;
} DNode, *DLinkList;

1. 双链表的插入操作

在双链表中p所指的结点之后插入结点*s:

  1. s->next = p->next;
  2. p->next->prior = s;
  3. s->prior = p;
  4. p->next = s;

2. 双链表的删除操作

2.3.4 循环链表

1. 循环单链表

2. 循环双链表

2.3.5 静态链表

2.3.6 顺序表和链表的比较

1. 存取方式

2. 逻辑结构与物理结构

3. 查找、插入和删除操作

4. 空间分配

2.3.7 选择

基于存储

基于运算

基于环境