一、代码定义单链表
#伪代码
struct LNode{ //LNode结点;定义单链表结点类型
ElemType data; //data数据域;每个节点存放一个数据元素
struct LNode *next; //next指针域;指针指向下一个节点
}LNode,*LinkList;
1.声明一个单链表时,只需声明一个头指针L,指向单链表的第一个结点;
2.上面代码段中两种重命名方式等价,按所需场景不同分别应用:
①强调是单链表时用LinkList L;
②强调是结点时用LNode* L。
3.下文中所提到的单链表的基本操作,其定义均以上文的代码段为准。
二、单链表的初始化
1.有头结点的单链表
①初始化函数
#伪代码
bool InitList(LinkList& L)
{
L = (LNode*)malloc(sizeof(LNode)); //分配一个头结点
if (L == NULL) //内存不足分配失败
return false;
L->next = NULL; //头结点之后还暂时没有节点
return true;
}
②判空函数
#伪代码
bool Empty(LinkList L)
{
if (L->next == NULL)
return true;
else
return false;
}
2.无头结点的单链表
①初始化函数
#伪代码
bool InitList(LinkList& L)
{
L = NULL; //空表 暂时还没有任何结点
return true;
}
②判空函数:
#伪代码
bool Empty(LinkList L)
{
return (L == NULL);
}
三、插入操作
1.按位序插入(带头结点)
①ListInsert(&L,i,e):插入操作:在表L中的第i个位置上插入指定元素e。
②代码实现:
#伪代码
bool ListInsert(LinkList& L, int i, int e)
{
if (i < 1)
return false;
LNode* p; //指针p指向当前扫描到的结点
int j = 0; //j代表当前p指向的是第几个结点
p = L; //L指向头结点,头结点是第0个结点,不存数据
while (p != NULL && j < i - 1) //循环找到第i-1个结点
{
p = p->next;
j++;
}
if (p == NULL) //i值不合法
return false;
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = p->next; //将结点s连到p之后
p->next = s; //将结点p连到s上
return true; //插入成功
}
③补充解释:要实现在第i个结点上插入,应该找到第i-1个结点然后对第i-1个结点的后继结点进行操作,所以while循环结束之前都是寻找第i-1个结点的操作;之后是对新结点的插入操作。
④此操作平均时间复杂度为O(n)。
2.按位序插入(无头结点)
①ListInsert(&L,i,e):插入操作:在表L中的第i个位置上插入指定元素e。
②区别在于不存在第0个结点,因此在i=1时要进行特殊处理。
③代码实现:
#伪代码
bool ListInsert(LinkList& L, int i, int e)
{
if (i < 1)
return false;
if (i == 1) //对i=1这一种特殊情况的操作,其余基本不变
{
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = L;
L = s;
return true;
}
LNode* p;
int j = 1; //这里j=1而不是=0,因为p指针刚开始指向的结点是第1个结点
p = L; //p指向第1个结点(注意:不是头结点)
while (p != NULL && j < i - 1)
{
p = p->next;
j++;
}
if (p == NULL)
return false;
LNode* s = (LNode*)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
return true;
}
④相比带头结点的单链表的ListInsert函数,无头结点的区别已在代码段中注释出来。
⑤此操作平均时间复杂度为O(n)。
3.指定结点后插
①InsertNextNode(LNode* p, ElemType e):在结点p后面插入元素e。
②代码实现:
#伪代码
bool InsertNextNode(LNode* p, int e)
{
if (p == NULL)
return false;
LNode* s = (LNode*)malloc(sizeof(LNode)); //要插入的新结点s
if (s == NULL) //某些情况下有可能会出现分配失败(如果内存不足)
return false;
s->data = e; //把e保存到s的数据域
s->next = p->next; //把插入位置后面的结点保存至新结点的指针域
p->next = s; //把新插入结点保存至p的指针域,即将结点s连到p后面
return true;
}
③时间复杂度O(1)。
④之前所提到的按位序插入操作中,在找到第i-1个结点后我们就可以直接用后插操作函数调用来简化按位序插入操作的函数了,如下:
4.指定结点前插
①InsertPriorNode(LNode* p, ElemType e) :在p结点之前插入元素e。
②代码实现:
#伪代码
bool InsertPriorNode(LNode* p, int e)
{
if (p == NULL)
return false;
LNode* s = (LNode*)malloc(sizeof(LNode));
if (s == NULL)
return false;
s->next = p->next; //将新的结点连到p之后
p->next = s; //将p连到s身上
s->data = p->data; //将s的数据域改写为p的数据域
p->data = e; //将p的数据域改写为e
return true;
}
③时间复杂度O(n)。
四、删除操作(带头结点)
1.按位序删除
①ListDelete(&L,i,&e):删除操作,删除表L中第i个位置的元素,并用e返回删除元素的值。
②代码实现:
#伪代码
bool ListDelete(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) //i值不合法
return false;
if (p->next == NULL) //当前p指向的是第i-1个结点,如果p.next为空的话,意味着第i位没有结点,就不能进行删除操作。
return false;
LNode* q = p->next; //p为第i-1位结点,p.next为第i位结点(即将被删除的结点),令q指向p.next
e = q->data; //用e来接收并返回即将删除结点的数据域的值
p->next = q->next; //将p连上被删除结点的后一个结点
free(q); //释放结点的存储空间
return true; //删除成功
}
③平均时间复杂度=O(n)。
2.指定结点的删除
①DeleteNode(LNode *p):删除指定结点p。
②代码实现:
#伪代码
bool DeleteNode(LNode* p)
{
if (p == NULL)
return false;
LNode* q = p->next; //令q指向*p的后继结点
p->data = p->next->data; //把p的数据域改写为p的后继结点的数据域,即和后继结点交换数据域
p->next = q->next; //把*q结点从链中断开
free(q); //释放后继结点的存储空间,也就是留下替身p,放逐本身q
return true;
}
③时间复杂度O(1)。
④上面的方法只适用于p不是表尾结点,如果要删除表尾结点的话,根本找不到p.next.data,我们只能从表头开始依次寻找p的前驱,所以需采用另一种方法如下:
#伪代码
void DeleteTail(LNode* L)
{
if (L == NULL)
{
printf("链表空");
return;
}
else
{
LNode* p;
LNode* q;
p = L->next; //p=L->next指第一个结点
q = L; //q=L指头结点
while (p->next != NULL) //使p指向表尾结点,q指向表尾结点的前驱结点
{
p = p->next;
q = q->next;
}
q->next = NULL; //先改指向,使p断连,然后再free,以防表尾结点指向未知的结点而没有指向空
free(p);
}
}
五、查找操作(带头结点)
1.按位查找
①GetElem(L,i):获取表L中第i个位置的元素的值,返回第i个元素。
②代码实现:
#伪代码
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;
}
③补充解释:上述代码段中注释那一行注意j<i,和前面找第i-1个结点不同,这里是直接找到第i个结点然后返回。
④平均时间复杂度=O(n)。
⑤这时,我们之前提到的按位插入和按位删除中寻找第i-1个结点的操作就可以封装为GetElem的函数调用,如下:
2.按值查找
①LocateElem(L,e):在表L中查找具有给定关键字值的元素,找到数据域==e的结点。
②代码实现:
#伪代码
LNode* LocateElem(LinkList L, int e)
{
LNode* p = L->next; //p指向第一个结点
while (p != NULL && p->data != e) //依次向后对比查找
{
p = p->next;
}
return p; //找到后返回p,找不到返回NULL
}
③平均时间复杂度=O(n)。
④上面的函数形参查找的为int类型数据,如果是struct类型则不能直接比较p->data!=e,而应该将struct中的每个元素都进行对比。
六、求表长
①length(LinkList L):返回单链表L的表长值。
②代码实现:
#伪代码
int length(LinkList L)
{
int len = 0; //返回值len
LNode* p = L;
while (p->next != NULL) //依次向后查找,len++
{
p = p->next;
len++;
}
return len;
}
③平均时间复杂度=O(n)。
七、打印单链表
①printlist(LinkList L):打印单链表L的内容。
②代码实现:
#伪代码
void printlist(LinkList L)
{
printf("该链表的内容为:");
while (L->next != NULL)
{
printf("%d ", L->next->data);
L = L->next;
}
printf("\n");
}
八、单链表的建立
1.尾插法
①LinkList List_TailInsert(LinkList& L)。
②代码实现:
#伪代码
LinkList List_TailInsert(LinkList& L) //正向建立单链表
{
int x; //设立elemtype为整型
L = (LinkList)malloc(sizeof(LNode)); //建立头结点
LNode* s, * r = L; //定义指针r和s均指向L,实际上r是我们需要的表尾指针
L->next = NULL;
scanf("%d", &x); //输入结点的值
while (x != -1) //-1判断循环结束,也可以写为其他的值
{
s = (LNode*)malloc(sizeof(LNode));
s->data = x;
r->next = s;
r = s; //永远保证r指向新的表尾结点
scanf("%d", &x);
}
r->next = NULL; //尾结点指针置空
return L;
}
③平均时间复杂度=O(n)。
④尾插法建立的单链表和用户输入的数据顺序相同,依次输入1,2,3,-1;所建立的单链表为1,2,3。
2.头插法
①LinkList List_HeadInsert(LinkList& L)。
②代码实现:
#伪代码
LinkList List_HeadInsert(LinkList& L) //逆向建立单链表
{
LNode* s;
int x;
L = (LinkList)malloc(sizeof(LNode)); //创建头结点
L->next = NULL; //初始为空链表
scanf("%d", &x); //输入新的结点
while (x != -1)
{
s = (LNode*)malloc(sizeof(LNode));
s->data = x; //把x赋值给新结点的数据域
s->next = L->next; //把新结点连到第1个结点上
L->next = s; //把头结点连接到新结点上,即完成将新结点插入表中,L为头指针
scanf("%d", &x);
}
return L;
}
③平均时间复杂度=O(n)。
④头插法一个重要的应用就是链表的逆置,依次扫描链表的每一个结点,然后将每个结点用尾插法插入。
九、最后给出一种单链表逆置的方法,如下:
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 = NULL;
return true;
}
LinkList List_TailInsert(LinkList& L)
{
int x;
L = (LinkList)malloc(sizeof(LNode));
LNode* s, * r = L;
L->next = NULL;
scanf("%d", &x);
while (x != -1)
{
s = (LNode*)malloc(sizeof(LNode));
s->data = x;
r->next = s;
r = s;
scanf("%d", &x);
}
r->next = NULL;
return L;
}
void printlist(LinkList L) {
printf("该链表的内容为:");
while (L->next != NULL) {
printf("%d ", L->next->data);
L = L->next;
}
printf("\n");
}
int main()
{
LNode* L = NULL;
InitList(L);
List_TailInsert(L);
printlist(L);
LNode* q = NULL;
LNode* p = L->next;
L->next = NULL;
while (p)
{
q = p;
p = p->next;
q->next = L->next;
L->next = q;
}
printlist(L);
}
代码解释:
1.定义单链表LNode;
2.所需用到的初始化、尾插法、打印函数和前文相同;
3.主函数部分:
①初始化L;
②用尾插法建立单链表,举例我们输入1,2,3,-1,建立的单链表为1,2,3;
③打印所建立的单链表;
④定义结点p、q,其中q置空,p指向第1个结点;
⑤将头结点置空,以便进行后续类似头插操作;
此时:
⑥while循环中:
第一次循环:
第二次循环:
第三次循环:
第四次循环时p判空,退出循环,观察我们得到的新的链表为:3,2,1。
最终代码调试结果: