目录
线性表的链式表示和实现
1、链表概念
链式存储结构:结点在存储器中的位置是任意的,即逻辑上相邻的数据元素在物理上不一定相邻
线性表的链式表示又称为非顺序映像或链式映像。
各结点由两个域组成:
- 数据域:存储元素数值数据
- 指针域:存储直接后继结点的存储位置
1.1与链式存储有关的术语
1、结点:数据元素的存储映像
2、链表:n个结点由指针链组成一个链表,它是线性表的链式存储映像,称为线性表的链式存储结构
3、单链表、双链表、循环链表:
- 结点只有一个指针域的链表,称为单链表或线性链表
- 结点有两个指针域的链表,称为双链表
- 首尾相接的链表称为循环链表
4、头指针、头结点和首元结点:
- 头指针:是指向链表中第一个结点的指针
- 首元结点:是指链表中存储第一个数据元素的结点
- 头结点:是在链表的首元结点之前附设的一个结点
1.2讨论
讨论1:如何表示空表?
- 无头结点时,头指针为空时表示空表
- 有头结点时,当头结点的指针域为空时表示空表
讨论2:在链表中设置头结点有什么好处?
1、便于首元结点的处理
首元结点的地址保存在头结点的指针域中,所以在链表的第一个位置的操作和其他位置一致,无需进行特殊处理;
2、便于空表和非空表统一处理
无论链表是否为空,头指针都是指向头结点的非空指针,因此空空表和非空表的处理也就统一了。
讨论3:头结点的数据域内装的是什么?
头结点的数据域可以为空,可以放置表长度,但此结点不能计入链表长度值。
2、单链表
2.1单链表的定义和表示
带头结点的单链表
单链表是由表头唯一确定,因此单链表可以用头指针的名字来命名,若头指针名是L,则把链表称为表L。
2.2单链表基本操作的实现
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int ElemType;
typedef int Status;
typedef struct Lnode {
int data;
struct Lnode* next;
}Lnode, * LinkList;
单链表的初始化(带头结点的单链表)
- 生成新结点作头结点,用头指针L指向头结点。
- 将头结点的指针域置空
Status InitList_L(LinkList* L) {
(*L) = (LinkList)malloc(sizeof(Lnode));
if (!(*L)) /* 存储分配失败 */
return ERROR;
(*L)->next = NULL;
return OK;
}
判断链表是否为空
- 判断头结点指针域是否为空
Status ListEmpty(LinkList L) {
if (L->next)
return FALSE;
else
return TRUE;
}
单链表的销毁
- 从头指针开始,依次释放所有结点
Status Destory_L(LinkList* L) {
Lnode* p;
while (L) {
p = *L;
*L = (*L)->next;
free(p);
}
return OK;
}
清空链表
- 依次释放所有结点,并将头结点指针域设置为空
Status ClearList(LinkList* L) {
Lnode* p, * q;
p = (*L)->next;
while (p) {
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL;
return OK;
}
求单链表的表长
- 从首元结点开始,依次计数所有结点
int LengthList_L(LinkList L) {
Lnode* p;
p = L->next;
int i = 0;
while (p) {
i++;
p = p->next;
}
return i;
}
取值——取单链表中第i个元素的内容
- 从链表的头指针出发,顺着链域next逐个结点往下搜索,直至搜索到第i个结点为止。
Status GetElem_L(LinkList L, int i, ElemType* e) {
int j = 1;
LinkList p = L->next;
while (p && j < i) {
p = p->next;
++j;
}
if (!p || j > i)
return ERROR;
*e = p->data;
return OK;
}
按值查找
- 根据指定数据获取改数据所在的位置(地址)
Lnode* LocateElem_L(LinkList L, ElemType e) {
LinkList p = L->next;
while (p && p->data != e) {
p = p->next;
}
return p;
}
- 根据指定数据获取该数据位置序号
int LElem_XL(LinkList L, ElemType e) {
LinkList p = L->next;
int i = 1;
while (p && p->data != e) {
p = p->next;
++i;
}
if (!p)
return ERROR;
else
return i;
}
插入——在第i个结点前插入值为e的新结点
Status ListInsert_L(LinkList* L, int i, ElemType e) {
Lnode* s;
s = (LinkList)malloc(sizeof(Lnode));
if (!s) /* 存储分配失败 */
return ERROR;
s->data = e;
Lnode* p = *L;
int j = 0;
while (p && j < i - 1) {
p = p->next;
++j;
}
if (!p || j > i - 1) {
return ERROR;
}
s->next = p->next;
p->next = s;
return OK;
}
删除——删除第i个结点并用e存储
Status ListDelete_L(LinkList* L, int i, ElemType* e) {
LinkList p = (*L);
int j = 0;
while (p->next && j < i - 1) {//从首元结点开始
p = p->next;
++j;
}
if (!(p->next) || j > i - 1)//防止输入i下限不合理
return ERROR;
Lnode* s;
s = p->next;
*e = s->data;
p->next = s->next;
free(s);
return OK;
}
建立单链表
- 头插法——元素插入在链表头部,也叫前插法
void CreateList_H(LinkList* L, int n) {
*L = (LinkList)malloc(sizeof(Lnode));
if (!(*L)) /* 存储分配失败 */
return;
(*L)->next = NULL;
Lnode* p;
for (int i = n; i > 0; i--) {
p = (LinkList)malloc(sizeof(Lnode));
if (!p) /* 存储分配失败 */
return ;
p->data = i;
p->next = (*L)->next;
(*L)->next = p;
}
}
- 尾插法——元素在链表尾部,也叫后插法
void LCreateList_R(LinkList* L, int n) {
*L = (LinkList)malloc(sizeof(Lnode));
if (!(*L)) /* 存储分配失败 */
return ;
(*L)->next = NULL;
Lnode* r = (*L);//尾指针r指向头结点
Lnode* p;
for (int i = n; i > 0; i--) {
p = (LinkList)malloc(sizeof(Lnode));
if (!p) /* 存储分配失败 */
return;
p->data = i;
p->next = NULL;
r->next = p;
r = p;//r指向新的尾结点
}
}