链式表的定义:
- 链式表(链表)是一种常见的线性表存储结构。它由一系列节点组成,每个节点包含数据域和指针域。数据域用于存储数据元素,指针域用于指向下一个节点(单链表情况)。
- 例如,一个简单的单链表节点结构体可以这样定义(以 C 语言为例)
typedef struct LNode { int data; // 数据域,这里假设存储整数 struct LNode *next; // 指针域,指向下一个节点 } LNode, *LinkList;
与顺序表不同,链表中的节点在内存中不是连续存储的,而是通过指针串联起来。
- 链表的类型
- 单链表
- 单链表的每个节点只有一个指针域,指向下一个节点。头节点通常用于方便操作,它的数据域可以不存储数据,也可以存储链表长度等信息。
- 例如,单链表的创建操作(创建一个只有头节点的空链表):
LinkList CreateList() { LinkList L = (LinkList)malloc(sizeof(LNode)); L->next = NULL; return L; }
- 单链表
- 双链表
- 双链表的节点有两个指针域,一个指向前一个节点,一个指向后一个节点。这样在某些操作(如逆向遍历)时会更加方便。双链表节点结构体定义如下:
typedef struct DNode {
int data;
struct DNode *prior; // 指向前一个节点
struct DNode *next; // 指向后一个节点
} DNode, *DLinkList;
- 循环链表
- 循环链表可以是单循环链表或双循环链表。单循环链表是最后一个节点的指针域指向头节点,形成一个环;双循环链表则是头节点的 prior 指针指向最后一个节点,最后一个节点的next 指针指向头节点。
- 链表的基本操作
- 插入操作
- 在单链表中插入节点:
- 假设要在节点 p 之后插入节点 q,操作步骤如下:
- q->next = p->next;
- p->next = q;
- 如果要在给定位置 i 插入节点(假设头节点位置为 0),需要先找到位置 i - 1 的节点,然后进行插入操作。
- 假设要在节点 p 之后插入节点 q,操作步骤如下:
- 在双链表中插入节点:
- 假设要在节点 p 之后插入节点 q,操作步骤如下:
- q->prior = p;
- q->next = p->next;
- if (p->next) p->next->prior = q;
- p->next = q;
- 假设要在节点 p 之后插入节点 q,操作步骤如下:
- 在单链表中插入节点:
- 删除操作
- 在单链表中删除节点 p 之后的节点:
- q = p->next;
- p->next = q->next;
- free(q);
- 在双链表中删除节点 p 之后的节点:
- q = p->next;
- p->next = q->next;
- if (q->next) q->next->prior = p;
- free(q);
- 在单链表中删除节点 p 之后的节点:
- 查找操作
- 按值查找:
- 在单链表中按值查找元素 x,从头节点开始遍历链表,比较每个节点的数据域,直到找到值为 x 的节点或者遍历完整个链表。
- 例如,单链表按值查找函数:
LNode* FindValue(LinkList L, int x) { LNode *p = L->next; while (p && p->data!= x) { p = p->next; } return p; }
- 按值查找:
- 插入操作
- 按位置查找:
- 在单链表中查找第 i 个节点,从头节点开始遍历,计数到第 i 个节点。
- 例如,单链表按位置查找函数:
LNode* FindPosition(LinkList L, int i) { int j = 0; LNode *p = L; while (p && j < i) { p = p->next; j++; } return p; }
- 链表的优缺点
- 优点
- 插入和删除操作灵活。在链表中插入或删除一个节点,只需要修改相关节点的指针,不需要像顺序表那样移动大量元素。例如,在单链表中间插入一个节点的时间复杂度为 O (1)(如果已知插入位置的前驱节点)。
- 链表的长度可以动态变化,不需要预先分配固定大小的存储空间,不会造成存储空间的浪费(如顺序表可能出现的空间分配过多或过少的情况)。
- 缺点
- 优点