考研 | 数据结构【第二章】线性表
文章目录
I. 线性表
- 定义: 线性表是具有相同数据类型的 n ( n ≥ 0 ) n\ (n\geq0) n (n≥0) 个数据元素的有限数列, 其中 n n n 为表长
- 基本操作:
InitList(&L): 初始化表. 构造一个空的线性表L, 分配内存空间 DestroyList(&L): 销毁表并释放线性表L所占用的内存空间. --- ListInsert(&L, i, e): 插入操作. 在表L中第i个位置插入指定元素e. ListDelete(&L, i, &e): 删除操作. 删除表L中第i个元素, 并用参数e返回删除元素的值. --- LocateElem(L, e): 按值查找 GetElem(L, i): 按位查找, 获取表L中第i个位置的元素的值 --- Length(L): 求表长 PrintList(L): 从前到后输出线性表L中所有元素 Empty(L): 判空操作
II. 顺序表
a. 定义
- 定义: 用 顺序存储 的方式实现 线性表 顺序存储. 把逻辑上相邻的元素存储在物理位置上也 相邻 的存储单元中.
- 静态分配:
#include <stdio.h> #define MaxSize 10 typedef struct { int data[MaxSize]; int length; }SqList; void InitList(SqList &L) { for(int i=0; i<MaxSize; i++) L.data[i] = 0; L.Length = 0; } int main() { SqList L; InitList(L); ... return 0; }
- 动态分配:
// 定义动态顺序表 typedef struct { int *data; int maxSize; int lenght; }seqDList; // 初始化动态顺序表 void initDList(seqDList &L) { L.data = (int *)malloc(MaxSize*sizeof(int)); L.maxSize = dftMaxSize; L.lenght = 0; } // 扩充动态顺序表 void increaseDList(seqDList &L, int len) { int* p = L.data; L.data = (int *)malloc((L.maxSize+len)*sizeof(int)); for (int i=0; i<L.lenght; i++) { L.data[i] = p[i]; } L.maxSize = L.maxSize + len; free(p); }
- 特点:
- 随机访问, 时间复杂度 O ( 1 ) O(1) O(1)
- 存储密度高, 每个结点只存储数据元素
- 扩展不方便
- 插入, 删除操作不方便, 需要移动大量元素
b. 插入和删除
- 插入:
时间复杂度: 最好 O ( 1 ) O(1) O(1), 最坏 O ( n ) O(n) O(n), 平均 O ( n ) O(n) O(n)// 在第i位插入元素 bool insertSList(seqSList &L, int i, int e) { if (i<1 || i>L.length+1) return false; if (L.length >= dftMaxSize) 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; }
- 删除:
时间复杂度: 最好 O ( 1 ) O(1) O(1), 最坏 O ( n ) O(n) O(n), 平均 O ( n ) O(n) O(n)// 删除第i位元素 bool deleteSList(seqSList &L, int i, int &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]; return true; }
c. 查找
- 按位查找:
时间复杂度 O ( 1 ) O(1) O(1)// 按位查找 int GetElem(seqSList &L, int i) { return L.data[i-1] }
- 按值查找:
时间复杂度: 最好 O ( 1 ) O(1) O(1), 最坏 O ( n ) O(n) O(n), 平均 O ( n ) O(n) O(n)// 按值查找 int LocateElem(seqSList &L, int e) { for (int i=0; i<L.length; i++) if (L.data[i]==e) return i+1; return 0; }
III. 单链表
a. 定义
用 链式存储 的存储结构进行存储的线性表叫做单链表.
- 带头结点的建立和初始化
typedef struct LNode { int data; struct LNode *next; }LNode, *linkList; // 带头结点初始化和判空 bool initList_h(linkList &L) { if (L==NULL) return false; L->next = NULL; return true; } bool empty_h(linkList &L) { if (L->next == NULL) return true; else return false; }
- 不带头节点的建立和初始化
// 不带头节点初始化和判空 bool initList(linkList &L) { L->next = NULL; return true; } bool empty(linkList L) { return L==NULL; }
b. 插入
- 带头结点的按位序插入: 时间复杂度 O ( n ) O(n) O(n)
// 带头结点按位序插入 bool listInsert(linkList &L, int i, int e) { if (i < 1) // i 代表的是第i个结点 return false; LNode *p; int j = 0; p = L; while (p!=NULL && j<i-1) { // 令p指到第i-1个结点位置采用后插法 p = p->next; j++; } if (p == NULL) return false; LNode *s = (LNode *)malloc(sizeof(LNode)); s->data = e; s->next = p->next; p->next = s; return true; }
- 不带头节点的按位序插入: 时间复杂度 O ( n ) O(n) O(n)
// 不带头节点按位序插入 bool listInsert(linkList &L, int i, int e) { if (i < 1) return false; if (i == 1) { LNode *s = (LNode *)malloc(sizeof(LNode)); s->data = e; s->next = L; L = s; return true; } LNode *p = L; int j = 1; while (p!=NULL && j < i-1) { p = p->next; j++; } if (p == NULL) return false; LNode *s = (LNode *)malloc(sizeof(LNode)); s->data = e; s->next = p->next; p->next = s; return true; }
- 指定结点的后插操作: 时间复杂度 O ( 1 ) O(1) O(1)
// 指定节点的后插操作 bool inserNextNode(LNode *p, int e) { if (p == NULL) return false; LNode *s = (LNode *)malloc(sizeof(LNode)); if (s == NULL) return false; s->data = e; s->next = p->next; p->next = s; return true; }
- 指定结点的前插操作: 时间复杂度位 O ( 1 ) O(1) O(1) 版本
本质上还是 后插法, 只不过是将新的结点插到 p p p 的后面, 然后令新节点的数据等于 p p p 的数据, 而 p p p 的数据又等于新的数据, 从而达到一个 “前插” 的效果.// 指定结点的前插操作 O(1) bool inserPriorNode(LNode *p, int e) { if (p == NULL) return false; LNode *s = (LNode *)malloc(sizeof(LNode)); if (s == NULL) return false; s->next = p->next; p->next = s; s->data = p->data; p->data = e; return true; }
c. 删除
- 带头节点按位序删除: 平均时间复杂度 O ( n ) O(n) O(n)
// 带头节点按位序删除 bool listDelete_h(linkList &L, int i, int &e) { if (i < 1) return false; LNode* p = L; int j = 0; while (p!=NULL && j<i-1) { // 将p指到第i-1个节点 p = p->next; j++; } if (p == NULL || p->next == NULL) // 表示第i-1或第i个节点不存在 return false; LNode *q = p->next; e = q->data; p->next = q->next; free(q); return true; }
- 指定结点删除: 时间复杂度 O ( 1 ) O(1) O(1) 版本 (偷天换日法)
bool deleteNode(LNode *p) { if (p == NULL || p->next == NULL) // 这种方法存在缺陷 // 当删除的结点位最后一个结点时,则只能从表头结点开始寻找p的前驱 return false; LNode *q = p->next; p->data = q->data; p->next = q->next; free(q); return true; }
d. 查找
- 带头结点的按位查找: 平均时间复杂度 O ( n ) O(n) O(n)
LNode * getElem(linkList L, int i) { if (i < 1) return NULL; LNode *p = L; int j = 0; while (p!=NULL && j<i) { p = p->next; j++; } return p; }
- 带头结点的按值查找: 平均时间复杂度 O ( n ) O(n) O(n)
LNode * locateElem(linkList L, int e) { LNode *p = L->next; while (p != NULL && p->data != e) p = p->next; return p; }
- 带头节点的求表长: 平均时间复杂度 O ( n ) O(n) O(n)
int listLen(linkList L) { int len = 0; LNode *p = L; while (p->next != NULL) { p = p->next; len++; } return len; }
e. 建立
- 带头结点的尾插法建立单链表:
// 带头结点的尾插法建立单链表 linkList tailBuildLink_h(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; r->next = s; r = s; scanf("%d", &x); } r->next = NULL; return L; }
- 带尾结点的尾插法建立单链表:
linkList headBuildLink_h(linkList &L) { int x; L = (linkList)malloc(sizeof(LNode)); L->next = NULL; LNode *s; scanf("%d", &x); while (x != 999) { s = (LNode *)malloc(sizeof(LNode)); s->data = x; s->next = L->next; L->next = s; scanf("%d", &x); } return L; }
IV. 双链表
- 定义结构体:
typedef struct dNode { int data; struct dNode *prior, *next; }dNode, *dLinkList;
- 初始化双链表:
bool initDLink(dLinkList &L) { L = (dLinkList)malloc(sizeof(dNode)); if (L == NULL) return false; L->prior = NULL; L->next = NULL; return true; }
- 判空:
bool emptyDLink(dLinkList &L) { return L->next == NULL; }
- 指定结点后插结点:
bool insertNextNode(dNode *p, dNode *s) { if (p == NULL || s == NULL) return false; s->prior = p; s->next = p->next; if (p->next != NULL) p->next->prior = s; p->next = s; return true; }
- 删除指定结点后一个结点:
bool deleteNextNode(dNode *p) { if (p == NULL || p->next == NULL) return false; dNode *q = p->next; if (q->next != NULL) q->next->prior = p; p->next = q->next; free(q); return true; }
- 销毁链表:
void destroyList(dLinkList &L) { while (L->next != NULL) deleteNextNode(L); free(L); L = NULL; }
V. 循环链表
a. 循环单链表
循环单链表: 表尾结点的 n e x t next next 指针指向头节点
对比单链表: 从一个结点出发可以找到其他任何一个结点
- 循环单链表结构定义:
typedef struct clNode { int data; struct clNode *next; }clNode, *clList;
- 循环单链表初始化:
bool initCList(clList &L) { L = (clNode *)malloc(sizeof(clNode)); if (L == NULL) return false; L->next = L; return true; }
- 循环单链表判空操作:
bool emptyCList(clList L) { return L->next == L; }
- 判断结点p是否为尾结点:
bool isTail(clList L, clNode *p) { return p->next == L; }
b. 循环双链表
循环双链表: 在双链表的基础上, 表头结点的 p r i o r prior prior 指向尾结点, 表尾结点的 n e x t next next 指向头节点
- 结构定义:
typedef struct cdNode { int data; struct cdNode *prior, *next; }cdNode, *cdLinkList;
- 初始化:
bool initCDLinkList(cdLinkList &L) { L = (cdNode *)malloc(sizeof(cdNode)); if (L == NULL) return false; L->prior = L; L->next = L; return true; }
- 判空 / 判尾:
bool isEmpty(cdLinkList L) { return L->next == L; } bool isTail(cdLinkList L, cdNode *p) { return p->next == L; }
- 在p结点后插入s结点:
bool insertNextNode(cdNode *p, cdNode *s) { if (p == NULL || s == NULL) return false; s->next = p->next; s->prior = p; p->next->prior = s; p->next = s; return true; }
- 删除p结点的后继结点:
bool deleteNextNode(cdNode *p, cdNode *q) { if (p == NULL || q == NULL) return false; p->next = q->next; q->next->prior = p; free(q); return true; }
VI. 静态链表
静态链表: 分配一整片连续的内存空间, 各个结点集中安置; 其中每个结点包含两部分: 数据和指向下一个结点的 “指针”
- 定义结构体:
#define maxSize 10
struct Node {
int data;
int next;
};
typedef struct Node sLinkList[maxSize];
- 简述基本操作实现:
- 查找: 从头节点出发依次往后遍历
- 插入位序为 i i i 的结点:
新建空结点存入数据;
从头节点找到第 i − 1 i-1 i−1 个结点;
将新结点的 n e x t next next 修改为第 i − 1 i-1 i−1 的 n e x t next next
将第 i − 1 i-1 i−1 结点的 n e x t next next 修改为新结点的位置 - 删除位序为 i i i 的结点:
从头结点出发找到第 i − 1 i-1 i−1 个结点
修改第 i − 1 i-1 i−1 个结点的 n e x t next next 指针
将第 i i i 个结点的 n e x t next next 指针设为 -2
VII. 顺序表和链表的比较
顺序表 | 链表 | |
---|---|---|
创建 | 需预分配空间,且不方便扩展 | 只需分配一个头结点,且方便扩展 |
销毁 | (静态数组)系统自动回收空间(动态数组)需要手动free | 每个结点依次free |
增删 | 需要移动大量元素 O ( n ) O(n) O(n) | 只需修改指针, 但查找也需 O ( n ) O(n) O(n) |
查找 | (按位查找) O ( 1 ) O(1) O(1);(按值查找) O ( log 2 n ) O(\log2n) O(log2n) | 都需要 O ( n ) O(n) O(n) |