本篇笔记课程来源:王道计算机考研 数据结构
其他常用操作:
静态分配:顺序表的表长确定后就无法更改
#define MaxSize 10 // 定义最大长度
typedef struct {
ElemType data[MaxSize]; // 用静态的“数组”存放数据元素
int length; // 顺序表的当前长度
} SqList; // 顺序表的类型定义(静态分配方式)
动态分配:使用 malloc 和 free 申请或释放内存空间
#define InitSize 10 // 顺序表的初始长度
typedef struct {
ElemType *data; // 指示动态分配数组的指针
int MaxSize; // 顺序表的最大容量
int length; // 顺序表的当前长度
}SeqList; // 顺序表的类型定义(动态分配方式)
ListInsert(&L,i,e):插入操作。在表 L 中的第 i 个位置(位序)上插入指定元素 e。
// 静态分配实现顺序表的代码省略...
bool ListInsert(SqList &L, int i, int e) {
if (i < 1 || i > L.length + 1) { // 判断 i 的范围是否有效
return false;
}
if (L.length == MaxSize) { // 当前存储空间已满,不能插入
return false;
}
for (int j = L.length; j >= i; j--) { // 将第 i 个元素及之后的元素后移
L.data[j] = L.data[j - 1];
}
L.data[i - 1] = e; // 在位置 i 处放入 e
L.length++; // 长度加 1
return true;
}
平均时间复杂度:O(n)
ListDelete(&L,i,&e):删除操作。删除表 L 中第 i 个位置的元素,并用 e 返回删除元素的值。
// 静态分配实现顺序表的代码省略...
bool ListDelete(SqList &L, int i, int &e) {
if (i < 1 || i > L.length) { // 判断 i 的范围是否有效
return false;
}
e = L.data[i - 1]; // 将被删除的元素赋值给 e
for (int j = i; j < L.length; j++) { // 将第 i 个位置后的元素前移
L.data[j - 1] = L.data[j];
}
L.length--; // 线性表长度减 1
return true;
}
平均时间复杂度:O(n)
GetElem(L,i):按位查找操作。获取表 L 中第 i 个位置的元素的值。
int GetItem(SqList L, int i) {
return L.data[i - 1];
}
时间复杂度:O(1)
LocateElem(L,e):按值查找操作。在表 L 中查找具有给定关键字值的元素。
==
仅适用于基本数据类型,结构体需要依次对比各个分量来判断是否相等。
int LocateElem(SqList L, int e) {
for (int i = 0; i < L.length; i++) {
if (L.data[i] == e) {
return i + 1; // 数组下标为 i 的元素值等于 e,返回其位序 i+1
}
}
return 0; // 退出循环,说明查找失败
}
时间复杂度:O(n)
struct LNode{ // 定义单链表结点类型
ElemType data; // 每个结点存放一个数据元素
struct LNode *next; // 指针指向下一个结点
};
// typedef struct LNode LNode; // 重命名
// typedef struct LNode *LinkList; // 重命名,强调这是一个单链表
// 新增一个结点,用 LNode * 强调这是一个结点
struct LNode *p = (struct LNode *)malloc(sizeof(struct LNode));
#include
#include
typedef struct LNode{ // 定义单链表结点类型
int data; // 每个结点存放一个数据元素
struct LNode *next; // 指针指向下一个结点
} LNode, *LinkList;
bool InitList(LinkList &L) {
L = NULL; // 空表,暂时还没有任何结点(防止脏数据)
return true;
}
void test() {
LinkList L; // 声明一个指向单链表的指针
InitList(L);
}
// 省略定义单链表的代码...
bool InitList(LinkList &L) {
L = (LNode *)malloc(sizeof(LNode)); // 分配一个头结点
if (L == NULL) { // 内存不足,分配失败
return false;
}
L->next = NULL; // 头结点之后暂时还没有结点
return true;
}
带头结点按位序插入:平均时间复杂度 O(n)
bool ListInsert(LinkList &L, int i, int e) {
if (i < 1) return false;
LNode *p; // 指针 p 指向当前扫描到的结点
int j = 0; // 当前 p 指向的是第几个结点
p = L; // L 指向头结点,头结点是第 0 个结点(不存数据)
while (p != NULL && j < i - 1) { // 循环找到第 i-1 个结点
p = p->next;
j++;
}
if (p == NULL) return false; // i 值不合法
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s; // 将结点 s 连到 p 之后
return true; // 插入成功
}
不带头结点按位序插入
bool ListInsert(LinkList &L, int i, int e) {
if (i < 1) return false;
if (i == 1) { // 插入第 1 个结点的操作与其他结点操作不同
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = L;
L = s; // 头指针指向新结点
return true;
}
LNode *p; // 指针 p 指向当前扫描到的结点
int j = 1; // 当前 p 指向的是第几个结点
p = L; // p 指向第一个结点(不是头结点)
while (p != NULL && j < i - 1) { // 循环找到第 i-1 个结点
p = p->next;
j++;
}
if (p == NULL) return false; // i 值不合法
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true; // 插入成功
}
指定结点的后插操作
bool InsertNextNode(LNode *p, int e) {
if (p == NULL) return false;
LNode *s = (LNode *)malloc(sizeof(LNode));
if (s == NULL) return false; // 内存分配失败
s->data = e; // 用结点 s 保存数据元素 e
s->next = p->next;
p->next = s; // 将结点 s 连到 p 之后
return true;
}
指定结点的前插操作:偷天换日
bool InsertPriorNode(LNode *node_p, int data) {
if (node_p == NULL) return false;
LNode *new_node = (LNode *)malloc(sizeof(LNode));
if (new_node == NULL) return false; // 内存分配失败
new_node->next = node_p->next;
node_p->next = new_node; // 新结点 new_node 连到 node_p 之后
new_node->data = node_p->data; // 将 node_p 中元素复制到 new_node 中
node_p->data = data; // node_p 中元素覆盖为 data
return true;
}
带头结点按位序删除:平均时间复杂度 O(n)
bool ListDelete(LinkList &L, int i, int &e) {
if (i < 1) return false;
LNode *p;
int j = 0;
p = L;
while (p->next != NULL && j < i - 1) {
p = p->next;
j++;
}
if (p == NULL) return false; // i 值不合法
if (p->next == NULL) return false; // 第 i-1 个结点之后已无其他结点
LNode *q = p->next; // 令 q 指向被删除的结点
e = q->data; // 用 e 返回元素的值
p->next = q->next; // 将 *q 结点从链中断开
free(q); // 释放结点的存储空间
return true; // 删除成功
}
删除指定结点:偷天换日
但是如果要删除最后一个结点,只能从链头开始查找
bool DeleteNode(LNode *p) {
if (p == NULL) return false;
LNode *q = p->next; // 另 q 指向 *p 的后继结点
p->data = p->next->data;// 和后继结点交换数据域
p->next = q->next; // 将 *q 结点从链中断开
free(q); // 释放后继结点的存储空间
return true;
}
按位查找:平均时间复杂度 O(n)
LNode * GetElem(LinkList L, int i) {
if (i < 0) return NULL;
LNode *p;
int j = 0;
p = L;
while (p != NULL && j < i) { // 循环找到第 i 个结点
p = p->next;
j++;
}
return p;
}
按值查找:平均时间复杂度 O(n)
LNode *LocateElem(LinkList L, int e) {
LNode *p = L->next;
// 从第一个结点开始查找数据域为 e 的结点
while (p != NULL && p->data != e) {
p = p->next;
}
return p; // 找到后返回该结点指针,否则返回 NULL
}
LinkList List_TaiInsert(LinkList &L) { // 正向建立单链表
int x;
L = (LinkList)malloc(sizeof(LNode)); // 建立头结点
LNode *s, *r = L; // r 为表尾指针
scanf("%d", &x);
while (x != 9999) { // 输入 9999 表示结束
s = (LNode *)malloc(sizeof(LNode));
s->data = x;
r->next = s;
r = s; // r 指向新的表尾结点
scanf("%d", &x);
}
r->next = NULL; // 表尾结点指针置空
return L;
}
LinkList List_HeadInsert(LinkList &L) { // 逆向建立单链表
LNode *s;
int x;
L = (LinkList)malloc(sizeof(LNode)); // 建立头结点
L->next = NULL; // 初始为空链表
scanf("%d", &x);
while (x != 9999) {
s = (LNode *)malloc(sizeof(LNode)); // 创建新结点
s->data = x;
s->next = L->next;
L->next = s; // 将新结点插入表中,L 为头指针
scanf("%d", &x);
}
return L;
}
双链表:在单链表的基础上,再增加一个指向前驱结点的指针域。
双链表的实现
typedef struct DNode {
ElemType data;
struct DNode *prior, *next;
} DNode, *DLinklist;
双链表的初始化
bool InitDLinkList(DLinklist &L) {
L = (DNode *)malloc(sizeof(DNode)); // 分配一个头结点
if (L == NULL) return false; // 内存不足,分配失败
L->prior = NULL; // 头结点的 prior 永远指向 NULL
L->next = NULL; // 头结点之后暂时还没有结点
return true;
}
双链表的后插操作
bool InsertNextDNode(DNode *p, DNode *s) {
if (p == NULL || s == NULL) return false;
s->next = p->next;
if (p->next != NULL) { // 如果 p 结点有后继结点
p->next->prior = s;
}
s->prior = p;
p->next = s;
return true;
}
双链表的后删操作
bool DeleteNextDNode(DNode *p) {
if (p == NULL) return false;
DNode *q = p->next; // 找到 p 的后继节点 q
if (q == NULL) return false; // p 没有后继结点
p->next = q->next;
if (q->next != NULL) { // q 结点不是最后一个结点
q->next->prior = p;
}
free(q);
return true;
}
双链表的销毁操作
void DestoryDLinklist(DLinklist &L) {
while (L->next != NULL) { // 循环释放各个数据结点
DeleteNextDNode(L);
}
free(L); // 释放头结点
L = NULL; // 头指针指向 NULL
}
双链表的遍历:链表不具备随机存取特性,查找操作只能通过顺序遍历实现,平均时间复杂度为 O(n)
循环单链表:表尾结点的 next 指针指向头结点。
从一个结点触发可以找到其他任何一个结点。
循环单链表初始化
typedef struct LNode {
int data;
struct LNode *next;
} LNode, *LinkList;
bool InitList(LinkList &L) {
L = (LNode *)malloc(sizeof(LNode)); // 分配一个头结点
if (L == NULL) return false; // 内存不足,分配失败
L->next = L; // 头结点 next 指向头结点
return true;
}
循环单链表判断是否为空
// 判断循环单链表是否为空
bool Empty(LinkList L) {
return L->next == L;
}
循环单链表判断结点是否为尾结点
bool isTail(LinkList L, LNode *p) {
return p->next == L;
}
循环双链表:表头结点的 prior 指向表尾结点;表尾结点的 next 指向头结点。
循环双链表初始化
typedef struct DNode {
int data;
struct DNode *next,*prior;
} DNode, *DLinklist;
bool InitDLinkList(DLinklist &L) {
L = (DNode *) malloc(sizeof(DLinklist));
if (L == NULL) return false;
L->next = L; // 头结点的 next 指向头结点
L->prior = L; // 头结点的 prior 指向头结点
return true;
}
循环双链表的判空和判尾和循环单链表一样。
循环双链表的后插和后删操作相较于双链表,不需要考虑前驱和后继是否为 NULL 的情况
addr + 8 * (i + 1)
#define MaxSize 10
typedef struct {
ElemType data;
int next;
} SLinkList[MaxSize];
bool InitSLinkList(SLinkList L) {
L[0].next = -1;
for (int i = 1; i < MaxSize; i++) {
L[i].next = -2;
}
return true;
}
顺序表(顺序存储) | 链表(链式存储) | |
---|---|---|
逻辑结构 | 都属于线性表,都是线性结构 | |
创建 | 需要预分配大片连续空间。若分配空间过小,则之后不方便拓展容量;若分配空间过大,则浪费内存资源 | 只需分配一个头结点或头指针,之后方便拓展 |
销毁 | 静态分配系统自动回收空间,动态分配需要手动free | 依次删除各个结点 |
插入和删除 | 需要将后续元素都后移 / 前移 | 只需修改指针即可 |
查找 | 按位查找T(n)=O(1),按值查找T(n)=O(n) | 按位查找和按值查找T(n)=O(n) |
优点 | 支持随机存取、存储密度高 | 离散的小空间分配方便,改变容量方便 |
缺点 | 大片连续空间分配不方便,改变容量不方便 | 不可随机存取,存储密度低 |