1 定义
线性结构是一个有序数据元素集合的逻辑结构,数据元素之间存在着一对一的线性关系。
- 集合中必存在唯一的一个“第一个元素”;
- 集合中必存在唯一的一个“最后的元素”;
- 除最后的元素之外,其他元素均有唯一的“后继”;
- 除第一个元素之外,其他元素均有唯一的“前驱”;
常见的线性结构有栈、队列、数组、字符串等。
2 顺序存储实现的线性表
2.1 结构定义
typedef int Status;
typedef int ElementType;
static int SUCCESS = 0;
static int ERROR = -1;
static int MAXSIZE = 100;
typedef struct {
ElementType *data;
int length;
} LinkList;
2.2 初始化
Status initLinkList(LinkList *l) {
l->data = malloc(sizeof(ElementType) * MAXSIZE);
if (l->data == NULL){
return ERROR;
}
l->length = 0;
return SUCCESS;
}
2.3 打印
Status traverseLinkList(LinkList l) {
printf("LinkList: ");
for (int i = 0; i < l.length; i++) {
printf("%d ", l.data[i]);
}
printf("\n");
return SUCCESS;
}
2.4 插入
Status insertIntoLinkList(LinkList *l, int i, ElementType e) {
// 检查位置 i 是否合法
if (i < 0 || i > l->length) {
return ERROR;
}
// 检查线性表是否已满
if (l->length >= MAXSIZE) {
return ERROR;
}
// 移动 i 及后面的元素以插入新的元素
if (i < l->length) {
// 向后移动元素
for (int j = l->length - 1; j >= i; j--) {
l->data[j + 1] = l->data[j];
}
}
// 赋值
l->data[i] = e;
// 修改长度
l->length++;
return SUCCESS;
}
2.5 删除
Status deleteFromLinkList(LinkList *l, int i, ElementType *e) {
// 检查位置 i 是否合法
if (i < 0 || i > l->length) {
return ERROR;
}
// 返回被删除的元素
*e = l->data[i];
// 向前移动元素
for (int j = i; j < l->length - 1; j++) {
l->data[j] = l->data[j + 1];
}
// 修改长度
l->length--;
return SUCCESS;
}
2.6 清空
Status clearLinkList(LinkList *l) {
// 只需将长度置为 0 即可,不需要抹除数据
l->length = 0;
return SUCCESS;
}
2.7 销毁
Status destroyLinkList(LinkList *l) {
free(l);
l = NULL;
return SUCCESS;
}
其他获取元素、获取长度、判断为空等方法与上述方法类似,比较简单不再赘述。
2.8 使用
int main() {
// 声明线性表
LinkList l;
printf("初始化线性表\n");
initLinkList(&l);
traverseLinkList(l);
printf("线性表插入\n");
for (int i = 0; i < 10; i++) {
insertIntoLinkList(&l, i, i * 2);
}
traverseLinkList(l);
printf("线性表插入 999\n");
insertIntoLinkList(&l, 4, 999);
traverseLinkList(l);
ElementType e;
deleteFromLinkList(&l, 5, &e);
printf("线性表删除 %d\n", e);
traverseLinkList(l);
printf("线性表清空\n");
clearLinkList(&l);
traverseLinkList(l);
printf("线性表销毁\n");
destroyLinkList(&l);
return 0;
}
打印结果:
初始化线性表
LinkList:
线性表插入
LinkList: 0 2 4 6 8 10 12 14 16 18
线性表插入 999
LinkList: 0 2 4 6 999 8 10 12 14 16 18
线性表删除 8
LinkList: 0 2 4 6 999 10 12 14 16 18
线性表清空
LinkList:
线性表销毁
3 单向链表
链式存储的特点是内存在物理层面上是不连续的,数据元素之间需要通过指针进行连接,构成逻辑意义上的线性表结构。
3.1 结构定义
typedef int Status;
typedef int ElementType;
static int SUCCESS = 0;
static int ERROR = -1;
/// 定义单向链表的节点
struct Node {
ElementType data; // 数据域
struct Node *next; // 指针域,指向逻辑中的下一个节点
};
typedef struct Node *LinkList;
3.2 初始化
/// 链表的初始化
Status initLinkList(LinkList *l) {
// 创建一个头结点,作为哨兵节点
*l = (LinkList)malloc(sizeof(struct Node));
// 内存分配失败
if (*l == NULL) {
return ERROR;
}
// 置空指针域
(*l)->next = NULL;
return SUCCESS;
}
初始化中创建了一个头结点作为辅助哨兵节点,好处在于当插入或删除时,不需要考虑该节点是否是头结点,从而省去一些不必要的判断。
3.3 打印
/// 链表的遍历打印
Status traverseLinkList(LinkList l) {
// 因为默认有头结点,所以从头结点的 next 开始
LinkList p = l->next;
printf("LinkList: ");
while (p) {
printf("%d ", p->data);
p = p->next;
}
printf("\n");
return SUCCESS;
}
3.4 插入
/// 单链表的插入
Status insertIntoLinkList(LinkList *l, int i, ElementType e) {\
int j = 1;
LinkList p = *l;
// 遍历链表寻找插入位置
while (p && j < i) {
p = p->next;
j++;
}
// 位置寻找错误
if (!p || j > i) {
return ERROR;
}
// 此时 p 为位置 i 的前一个节点
// 创建一个新的节点
LinkList s = (LinkList)malloc(sizeof(LinkList *));
// 数据域赋值
s->data = e;
// 将 p 的后继赋值给新的节点
s->next = p->next;
// 将 s 作为 p 新的后继
p->next = s;
return SUCCESS;
}
3.5 取值
/// 链表的取值
Status getElementFromLinkList(LinkList l, int i, ElementType *e) {
int j = 1;
LinkList p = l->next;
// 遍历链表寻找读取位置
while (p && j < i) {
p = p->next;
j++;
}
if (!p || j > i) {
return ERROR;
}
// 此时 p 为位置 i 上的节点
*e = p->data;
return SUCCESS;
}
寻找位置的方式和插入时类似,其中 while (p && j <= i) 的结束条件是 j == i,所以找到的是 i 上的节点,和插入时的 while (p && j < i) 位置不同。
3.6 删除
/// 删除单链表的节点
Status deleteFromLinkList(LinkList *l, int i, ElementType *e) {
int j = 1;
LinkList p = (*l)->next;
// 遍历链表寻找删除位置
while (p && j < (i - 1)) {
p = p->next;
j++;
}
// 位置寻找错误
if (!p || j > (i - 1)) {
return ERROR;
}
// 此时 p 为位置 i 的前一个节点
// 获取被删除的节点
LinkList s = p->next;
// 数据返回
*e = s->data;
// 指针指向被删除d节点的下一个节点
p->next = p->next->next;
// 释放被删除的节点
free(s);
return SUCCESS;
}
3.7 完整调用
int testLinkList02() {
// 节点声明
LinkList l;
// 初始化链表头结点
if (initLinkList(&l) == SUCCESS) {
printf("链表初始化成功\n");
} else {
printf("链表初始化失败\n");
}
printf("链表插入数据\n");
for (int i = 0; i < 10; i++) {
insertIntoLinkList(&l, 1, i);
}
traverseLinkList(l);
ElementType e1;
getElementFromLinkList(l, 3, &e1);
printf("链表获取第3个数据 %d\n", e1);
traverseLinkList(l);
ElementType e2;
deleteFromLinkList(&l, 4, &e2);
printf("链表删除第4个数据 %d\n", e2);
traverseLinkList(l);
printf("清空链表\n");
clearLinkList(&l);
traverseLinkList(l);
return 0;
}
打印结果:
链表初始化成功
链表插入数据
LinkList: 9 8 7 6 5 4 3 2 1 0
链表获取第3个数据 7
LinkList: 9 8 7 6 5 4 3 2 1 0
链表删除第4个数据 6
LinkList: 9 8 7 5 4 3 2 1 0
清空链表
LinkList:
3.8 头插法构建链表
/// 头插法构建链表
Status createLinkListByHead(LinkList *l, int n) {
// 创建一个头节点作为辅助哨兵节点
*l = (LinkList)malloc(sizeof(LinkList *));
(*l)->next = NULL;
// 循环插入 n 个节点
LinkList p;
for (int i = 0; i < n; i++) {
// 创建新的节点并赋值
p = (LinkList)malloc(sizeof(LinkList *));
p->data = i;
// 新的节点作为原首节点的前驱节点
p->next = (*l)->next;
// 重置头结点的后继为新节点
(*l)->next = p;
}
return SUCCESS;
}
int main() {
LinkList p;
printf("尾插法插入数据\n");
createLinkListByTail(&p, 5);
traverseLinkList(p);
}
打印结果:
尾插法插入数据
LinkList: 0 1 2 3 4
3.9 尾插法构建链表
/// 尾插法构建链表
Status createLinkListByTail(LinkList *l, int n) {
// 创建一个头节点作为辅助哨兵节点
*l = (LinkList)malloc(sizeof(LinkList *));
(*l)->next = NULL;
// 记录当前节点
LinkList q = *l;
LinkList p;
// 循环插入 n 个节点
for (int i = 0; i < n; i++) {
// 创建新的节点并赋值
p = (LinkList)malloc(sizeof(LinkList *));
p->data = i;
p->next = NULL;
// 将新节点作为链表尾节点的后继
q->next = p;
// 重置尾节点
q = p;
}
return SUCCESS;
}
int main() {
LinkList p;
printf("头插法插入数据\n");
createLinkListByHead(&p, 5);
traverseLinkList(p);
}
打印结果:
头插法插入数据
LinkList: 4 3 2 1 0