声明:本博客代码来自于《大话数据结构》一书中的源代码,外加我写的注释。
一、部分代码(函数)(链表的基本操作)
一、链表结点的构建
目录
typedef int ElemType;/* ElemType类型根据实际情况而定,这里假设为int */
typedef struct Node // @ "Node"可要可不要
{
ElemType data;
struct Node* next; // 但为了定义指针域,故需要写“Node”
}Node;
typedef struct Node* LinkList; /* 定义LinkList */
// @ 则LinkList表示一个类型: 节点地址变量类型
其中 typedef 的意思是:自己定义一个类型,比如熟知的有int、float、double等类型,typedef int ElemType,其实就是给int起了个别名,ElemType data 等价于 int data,ElemType也即表示int类型。
二、初始化链表
初始化链表,顾名思义,就是类似于 int a(初始化一个int变量),初始化但不赋值(只是单纯创建了这么一个东西,至于是啥,先不管它)。此处需注意:链表与数组等不同,当初始化一个链表,或是想要添加一个链表结点,都必须先用malloc函数,为其申请一块内存地址区域,以便存入数据。本博客代码来自于《大话数据结构》一书中的源代码,外加我写的注释
typedef int Status;
Status InitList(LinkList* L) // @ 这里的传参是 【头指针的指针】
{ // @ LinkList本身就是 链表结点的指针了,传入*L,是因为需要改变L,所以传入的是【头结点指针的指针】
*L = (LinkList)malloc(sizeof(Node)); /* 产生头结点,并使L指向此头结点 */
if ((*L) == NULL) /* 存储分配失败 */ // @ <---> if(!(*L))
return ERROR;
(*L)->next = NULL; /* 指针域为空 */
return OK;
}
三、判断链表是否为空表
/* 初始条件:链式线性表L已存在。操作结果:若L为空表,则返回TRUE,否则返回FALSE */
Status ListEmpty(LinkList L) // @ 判断是否为空表
{ // @ 这里的传参是【头指针】其->next 才是真正的 第一个存数据的结点
if (L->next)
return FALSE;
else
return TRUE;
}
四、清除链表
Status ClearList(LinkList* L)
{ // @ 即 销毁整个链表
LinkList p, q;
p = (*L)->next; /* p指向第一个结点 */
// 错误:p = (*L) while(p->next) 错因:*L是头结点,不能把头结点删除了呀,头结点本身不存数据,没有数据域,得把头结点之后的所有删掉才正确
while (p) /* 没到表尾 */
{
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL; /* 头结点指针域为空 */
return OK;
}
五、链表数据元素个数
/* 返回L中数据元素个数 */
int ListLength(LinkList L)
{
int i = 0;
LinkList p = L->next; /* p指向第一个结点 */
while (p)
{ // for (LinkList p = L->next; p; p = p->next)
i++; // i++;
p = p->next;
}
return i; // @ 如果一次while循环都没有,那i=0,结点数为0
}
六、查找链表第 i 个元素
/* 操作结果:用e返回L中第i个数据元素的值 */
Status GetElem(LinkList L, int i, ElemType* e)
{ // @ 因为e需要带回值,所以此处传参为指针类型
int j = 1; /* j为计数器 */
LinkList p; /* 声明一结点p */
p = L->next; /* 让p指向链表L的第一个结点 */
while (p && j < i) /* p不为空或者计数器j还没有等于i时,循环继续 */
{ // @ 如果一切顺利,应有j = i
p = p->next; /* 让p指向下一个结点 */
++j;
}
if (!p || j > i) // @ 对while循环 为什么停止 做出判断
return ERROR; /* 第i个元素不存在 */
// @ 只有 (p && j == i) 这一种正确情况
*e = p->data; /* 取第i个元素的数据 */
return OK; // @ 一个while,一个if,最后只剩 j=i 这一个条件了
}
七、查找链表数据位序
/* 操作结果:返回L中第1个与e满足关系的数据元素的位序。 */
/* 若这样的数据元素不存在,则返回值为0 */
int LocateElem(LinkList L, ElemType e)
{
int i = 0;
LinkList p = L->next;
while (p)
{
i++;
if (p->data == e) /* 找到这样的数据元素 */
return i;
p = p->next;
}
return 0;
}
八、“增”
/* 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 */
Status ListInsert(LinkList* L, int i, ElemType e)
{
int j = 1;
LinkList p, s;
p = *L; // @ 注意:你想要的是循环之后,p得是插入位置的 前面的那个节点
// @ 所以,p=*L 而不是 p=(*L)->next;
while (p && j < i) /* 寻找第i个结点 */
{ // @ for循环也可以“for (; p && j < i; j++) p = p->next;”
p = p->next;
++j;
}
if (!p || j > i) // @ 比如插在第2个,但连第一个节点都是NULL,怎么插??
return ERROR; /* 第i个元素不存在 */
s = (LinkList)malloc(sizeof(Node)); /* 生成新结点(C语言标准函数) */
s->data = e;
s->next = p->next; /* 将p的后继结点赋值给s的后继 */
p->next = s; /* 将s赋值给p的后继 */
return OK;
}
九、“删”
关于这个while循环里的条件,为什么 插入 和 删除 不一样呢,插入是while(p); 删除是while(p->next);
想一下,插入在第5个位置,链表有四个节点就可以了,但是,删除不一样,删除第5个,第5个必须不为空才行
区别就在于 第i个位置的那个节点,插入可以为空,而删除不能为空
/* 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1 */
Status ListDelete(LinkList* L, int i, ElemType* e)
{
LinkList q, p = *L;
int j = 1;
while (p->next && j < i)
{
p = p->next;
j++;
}
if (!(p->next) || j > i)
return ERROR;
q = p->next;
*e = q->data;
p->next = q->next;
free(q);
return OK;
}
十、遍历
/* 操作结果:依次对L的每个数据元素输出 */
Status ListTraverse(LinkList L)
{ // @ tarverse: 穿过,越过;这里引申为 遍历
LinkList p = L->next;
while (p)
{
visit(p->data);
p = p->next;
}
printf("\n");
return OK;
}
十一、头插法 创建链表
/* 随机产生n个元素的值,建立带表头结点的单链线性表L(头插法) */
void CreateListHead(LinkList* L, int n)
{ // @ 头插法
LinkList p;
int i;
srand(time(0)); /* 初始化随机数种子 */
// @ 这个句子的意思是每次运行,都是随机数。如果没有,虽然也是随机数,但每次运行都一样,不能做到绝对随机
// @ 比如在这个句子里,没有随机句子(此句)则每次运行都是37 28 43 96 62 28 82 46 6 65 63 59 79 25 70 1 35 68 42
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL; /* 先建立一个带头结点的单链表 */
for (i = 0; i < n; i++)
{
p = (LinkList)malloc(sizeof(Node)); /* 生成新结点 */
p->data = rand() % 100 + 1; /* 随机生成100以内的数字 */
p->next = (*L)->next;
(*L)->next = p; /* 插入到表头 */
}
}
十二、尾插法 创建链表
/* 随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法) */
void CreateListTail(LinkList* L, int n)
{ // @ 尾插法
LinkList p, r;
int i;
srand(time(0)); /* 初始化随机数种子 */
// @ 92 5 3 54 93 83 22 17 19 96 48 27 72 39 70 13 6
*L = (LinkList)malloc(sizeof(Node)); /* L为整个线性表 */
r = *L; /* r为指向尾部的结点 */
for (i = 0; i < n; i++)
{
p = (Node*)malloc(sizeof(Node)); /* 生成新结点 */
p->data = rand() % 100 + 1; /* 随机生成100以内的数字 */
r->next = p; /* 将表尾终端结点的指针指向新结点 */
r = p; /* 将当前的新结点定义为表尾终端结点 */
}
r->next = NULL; /* 表示当前链表结束 */
}
二、完整代码
#include "stdio.h"
#include "string.h"
#include "ctype.h"
#include "stdlib.h" // @ malloc
#include "math.h"
#include "time.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存储空间初始分配量 */
typedef int Status;/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int ElemType;/* ElemType类型根据实际情况而定,这里假设为int */
Status visit(ElemType c)
{
printf("%d ", c);
return OK;
}
typedef struct Node // @ "Node"可要可不要
{
ElemType data;
struct Node* next;
}Node;
typedef struct Node* LinkList; /* 定义LinkList */
// @ 则LinkList表示 节点地址变量类型
// @ 《 初始化 》 中 11.22 ×
/* 初始化链式线性表 */
Status InitList(LinkList* L) // @ 这里的传参是 【头指针的指针】
{ // @ LinkList本身就是 链表结点的指针了,传入*L,是因为需要改变L,所以传入的是【头结点指针的指针】
*L = (LinkList)malloc(sizeof(Node)); /* 产生头结点,并使L指向此头结点 */
if ((*L) == NULL) /* 存储分配失败 */ // @ <---> if(!(*L))
return ERROR;
(*L)->next = NULL; /* 指针域为空 */
return OK; // @ 如果全是*L,为什么不直接传入“L”呢
}
/* 初始条件:链式线性表L已存在。操作结果:若L为空表,则返回TRUE,否则返回FALSE */
Status ListEmpty(LinkList L) // @ 判断是否为空表
{ // @ 这里的传参是【头指针】其->next 才是真正的 第一个存数据的结点
if (L->next)
return FALSE;
else
return TRUE;
}
// @ 中
// 《重置空表》ClearList 11.22 ×
Status ClearList(LinkList* L)
{ // @ 即 销毁整个链表
LinkList p, q;
p = (*L)->next; /* p指向第一个结点 */
// 错误:p = (*L) while(p->next) 错因:*L是头结点,不能把头结点删除了呀,
// 头结点本身不存数据,没有数据域,得把头结点之后的所有删掉才正确
while (p) /* 没到表尾 */
{
q = p->next;
free(p);
p = q;
}
(*L)->next = NULL; /* 头结点指针域为空 */
return OK;
}
// @ 《 计数 》 中 11.22 √×
/* 返回L中数据元素个数 */
int ListLength(LinkList L)
{
int i = 0;
LinkList p = L->next; /* p指向第一个结点 */
while (p)
{ // for (LinkList p = L->next; p; p = p->next)
i++; // i++;
p = p->next;
}
return i;
}
// @ 《 查找 》 难 10.21 ×
/* 操作结果:用e返回L中第i个数据元素的值 */
Status GetElem(LinkList L, int i, ElemType* e)
{ // @ 因为e需要带回值,所以此处传参为指针类型
int j = 1; /* j为计数器 */
LinkList p; /* 声明一结点p */
p = L->next; /* 让p指向链表L的第一个结点 */
while (p && j < i) /* p不为空或者计数器j还没有等于i时,循环继续 */
{ // @ 如果一切顺利,应有j = i
p = p->next; /* 让p指向下一个结点 */
++j;
}
if (!p || j > i) // @ 对while循环 为什么停止 做出判断
return ERROR; /* 第i个元素不存在 */
// @ 只有 (p && j == i) 这一种正确情况
*e = p->data; /* 取第i个元素的数据 */
return OK; // @ 一个while,一个if,最后只剩 j=i 这一个条件了
}
// 《 定位 》 中 10.17 √
/* 操作结果:返回L中第1个与e满足关系的数据元素的位序。 */
/* 若这样的数据元素不存在,则返回值为0 */
int LocateElem(LinkList L, ElemType e)
{
int i = 0;
LinkList p = L->next;
while (p)
{
i++;
if (p->data == e) /* 找到这样的数据元素 */
return i;
p = p->next;
}
return 0;
}
// 《 插入 》《 增 》 难 10.21 ×
/* 操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1 */
Status ListInsert(LinkList* L, int i, ElemType e)
{
int j = 1;
LinkList p, s;
p = *L; // @ 注意:你想要的是循环之后,p得是插入位置的 前面的那个节点
// @ 所以,p=*L 而不是 p=(*L)->next;
while (p && j < i) /* 寻找第i个结点 */
{ // @ for循环也可以“for (; p && j < i; j++) p = p->next;”
p = p->next;
++j;
}
if (!p || j > i) // @ 比如插在第2个,但连第一个节点都是NULL,怎么插??
return ERROR; /* 第i个元素不存在 */
s = (LinkList)malloc(sizeof(Node)); /* 生成新结点(C语言标准函数) */
s->data = e;
s->next = p->next; /* 将p的后继结点赋值给s的后继 */
p->next = s; /* 将s赋值给p的后继 */
return OK;
}
/* @ 关于这个while循环里的条件,为什么 插入 和 删除 不一样呢,插入是while(p); 删除是while(p->next);
想一下,插入在第5个位置,链表有四个节点就可以了,但是,删除不一样,删除第5个,第5个必须不为空才行
区别就在于 第i个位置的那个节点,插入可以为空,而删除不能为空 */
// @ 《 删除 》 难 10.21 ×
/* 操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1 */
Status ListDelete(LinkList* L, int i, ElemType* e)
{
LinkList q, p = *L;
int j = 1;
while (p->next && j < i)
{
p = p->next;
j++;
}
if (!(p->next) || j > i)
return ERROR;
q = p->next;
*e = q->data;
p->next = q->next;
free(q);
return OK;
}
// @ 《 遍历 》 中 10.21 × (输出加空格,换行!!)
/* 操作结果:依次对L的每个数据元素输出 */
Status ListTraverse(LinkList L)
{ // @ tarverse: 穿过,越过;这里引申为 遍历
LinkList p = L->next;
while (p)
{
visit(p->data);
p = p->next;
}
printf("\n");
return OK;
}
// @@@@ 列表的逆置 自己添加的
//LinkList ListReverse(LinkList L)
//{
// if (L == NULL)
// return NULL;
// LinkList front, behind, temp;
// front = NULL;
// behind = L;
// while (behind)
// {
// temp = behind->next;
// behind->next = front;
// front = behind;
// behind = temp;
// }
// return (front);
// // 此时 最后一个节点是front,behind已经是NULL了。(while()判断里面,behind为假)
//}
// @ 《 新建 头插法 》 很难
/* 随机产生n个元素的值,建立带表头结点的单链线性表L(头插法) */
void CreateListHead(LinkList* L, int n)
{ // @ 头插法
LinkList p;
int i;
srand(time(0)); /* 初始化随机数种子 */
// @ 这个句子的意思是每次运行,都是随机数。如果没有,虽然也是随机数,但每次运行都一样,不能做到绝对随机
// @ 比如在这个句子里,没有随机句子(此句)则每次运行都是37 28 43 96 62 28 82 46 6 65 63 59 79 25 70 1 35 68 42
*L = (LinkList)malloc(sizeof(Node));
(*L)->next = NULL; /* 先建立一个带头结点的单链表 */
for (i = 0; i < n; i++)
{
p = (LinkList)malloc(sizeof(Node)); /* 生成新结点 */
p->data = rand() % 100 + 1; /* 随机生成100以内的数字 */
p->next = (*L)->next;
(*L)->next = p; /* 插入到表头 */
}
}
// @ 《 新建 尾插法 》 很难
/* 随机产生n个元素的值,建立带表头结点的单链线性表L(尾插法) */
void CreateListTail(LinkList* L, int n)
{ // @ 尾插法
LinkList p, r;
int i;
srand(time(0)); /* 初始化随机数种子 */
// @ 92 5 3 54 93 83 22 17 19 96 48 27 72 39 70 13 6
*L = (LinkList)malloc(sizeof(Node)); /* L为整个线性表 */
r = *L; /* r为指向尾部的结点 */
for (i = 0; i < n; i++)
{
p = (Node*)malloc(sizeof(Node)); /* 生成新结点 */
p->data = rand() % 100 + 1; /* 随机生成100以内的数字 */
r->next = p; /* 将表尾终端结点的指针指向新结点 */
r = p; /* 将当前的新结点定义为表尾终端结点 */
}
r->next = NULL; /* 表示当前链表结束 */
}
int main()
{
LinkList L;
ElemType e; // @ 好厌,tnnd,变量e i j k 都是int类型,非得给它取个别名,显得代码高大上吗
Status i;
int j, k;
i = InitList(&L);
printf("初始化L后:ListLength(L)=%d\n", ListLength(L));
for (j = 1; j <= 5; j++)
i = ListInsert(&L, 1, j);
printf("在L的表头依次插入1~5后:L.data=");
ListTraverse(L);
printf("ListLength(L)=%d \n", ListLength(L));
i = ListEmpty(L);
printf("L是否空:i=%d(1:是 0:否)\n", i);
i = ClearList(&L);
printf("清空L后:ListLength(L)=%d\n", ListLength(L));
i = ListEmpty(L);
printf("L是否空:i=%d(1:是 0:否)\n", i);
for (j = 1; j <= 10; j++)
ListInsert(&L, j, j);
printf("在L的表尾依次插入1~10后:L.data=");
ListTraverse(L);
printf("ListLength(L)=%d \n", ListLength(L));
ListInsert(&L, 1, 0);
printf("在L的表头插入0后:L.data=");
ListTraverse(L);
printf("ListLength(L)=%d \n", ListLength(L));
GetElem(L, 5, &e);
printf("第5个元素的值为:%d\n", e);
for (j = 3; j <= 7; j++)
{
k = LocateElem(L, j);
if (k)
printf("第%d个元素的值为%d\n", k, j);
else
printf("没有值为%d的元素\n", j);
}
k = ListLength(L); /* k为表长 */
for (j = k + 1; j >= k; j--)
{
i = ListDelete(&L, j, &e); /* 删除第j个数据 */
if (i == ERROR)
printf("删除第%d个数据失败\n", j);
else
printf("删除第%d个的元素值为:%d\n", j, e);
}
printf("依次输出L的元素:");
ListTraverse(L);
j = 5;
ListDelete(&L, j, &e); /* 删除第5个数据 */
printf("删除第%d个的元素值为:%d\n", j, e);
printf("依次输出L的元素:");
ListTraverse(L);
i = ClearList(&L);
printf("\n清空L后:ListLength(L)=%d\n", ListLength(L));
CreateListHead(&L, 20);
printf("整体创建L的元素(头插法):");
ListTraverse(L);
i = ClearList(&L);
printf("\n删除L后:ListLength(L)=%d\n", ListLength(L));
CreateListTail(&L, 20);
printf("整体创建L的元素(尾插法):");
ListTraverse(L);
return 0;
}