实验环境
环境:visual studio 2019
语言:C
基本介绍
1、什么是链表
根据一个结点可以找到唯一一个下一个结点的线性表称为链表(链表的名字由来可参照其数据结构的特点,因其每个结点都有下个结点的指针,当形成线性表后,如同自行车的链条一样,故称链表)。
2、链表的数据模型:
数据域(data):用以存放数据的
指针域(next):用以存放下一个结点的指针(地址)
(而我们画简图时的箭头表示指针的指向关系,也可理解为结点与结点之间的关系)
3、链表的分类:
a、单向链表:根据一个结点可以访问其下一个结点(未结点除外,因为未结点的指针域未空),通常这种链表的数据结构只有一个指针域,指向下一个结点
b、双向链表:根据一个结点既可以访问其前一个结点,又可访问其后一个结点(首、未结点除外,这两个结点中的两个指针域必有一个指针域为空)。通常这种链表的数据结构有两个指针域,一个指针域存放下一个结点指针,另一个指针域存放上一个结点的指针
c、循环链表:即最后一个结点的next域存放的是第一个结点(首元结点)的指针,这个链表一般不设头结点(当然也可以设),这样就形成了一个链表环,按照上面的两种链表的链接方式又可分为单向循环链表和双向循环链表
4、通常链表有以下几个特征:
a、头指针(一般指向头结点或者首元结点),这个指针很重要,在操作链表时需要通过头指针对链表进行操作
b、头结点:头结点的指针域一般为第一个结点(也称首元结点),数据域可以用来存放链表的长度等信息或者为空,头结点可以有也可以没有,但要注意的是,当有头结点和没有头结点时,在许多操作上有许多不同之处
c、首元结点:即第一个结点。
数据结构分析
由上面的基本介绍,我们可以用代码实现其数据模型(以单链表为例)
typedef struct LNode {
ElemType data;
struct LNode* next;
}LNode, * LinkList;
注意:此处定义了两种类型,一个是普通的结构体类型,一个是结构体的指针类型,我们分析一下使用两种类型来定义变量的区别:
LNode L;定义普通的LNode类型的变量,次时的L只是普通的LNode类型变量
LinkList L:定义指针类型的变量,其基类型为LNode, 此时的L是指针变量该定义相当于 LNode *L;
准备工作和方法清单
1、创建工程
此实验使用分文件编写
LinkList.h:编写需要定义的宏常量,引入头文件、结构体的定义、以及方法的声明(下面的方法声明即是本实验的方法清单,下面不再列举)
#pragma once
#ifndef LinkList_H
#define LinkList_H
#define OVERFLOWW -1
#define OK 1
#define ERROR 0
#define TRUE 1
#define FLASE 0
#include <stdio.h>
#include<stdlib.h>
#include<malloc.h>
typedef int Status;
typedef int ElemType;
typedef struct LNode {
ElemType data;
struct LNode* next;
}LNode, * LinkList;
//创建头结点
extern LinkList CreateHead();
//用表头插入法逆序建立带头结点的单链表单链表
extern Status CreatLinkList1(LinkList, int, ElemType*);
//用表尾插入法建立带头结点的单链表
extern Status CreatLinkList2(LinkList, int, ElemType*);
//采用交互方式实现链表的创建(L为头指针,n代表链表的长度)
extern Status CreatLinkList3(LinkList, int);
//在带头结点的单链表L中第i个位置之前插入元素e
extern Status InsertLinkLNode(LinkList, int, ElemType);
//在带头结点的单链表L中删除第i个位置的元素,并由e带回
extern Status Del_LinkLNode1(LinkList , int , ElemType*);
//在带头结点的单链表L中删除指定的键值出现的第一个结点,并返回OK或者FLASE
extern Status Del_LinkLNode2(LinkList , ElemType);
//在带头结点的单链表L中删除指定的键值出现的所有结点,并返回OK或者FLASE
extern Status Del_LinkLNode3(LinkList , ElemType);
//归并两个非递减的链表A,B到C中,且C非递减
// A的头结点归并到C中,作为C的头结点
extern Status Mergesort(LinkList, LinkList, LinkList);
//打印链表
extern void PrintLinkList(LNode );
//释放链表资源
extern void FreeLinkList(LinkList);
#endif // !LinkList_H
LinkList.c:编写对方法的实现,里面的内容在,在下面的方法实现中进行讲解
main.c:编写测试代码,在代码测试中讲解
方法实现
引入头文件,对里面的方法进行实现
#include "LinkList.h"
1、创建头结点
方法功能:创建头结点,并将头结点的地址返回
//返回值为指向头结点的头指针(即头结点的地址)
LinkList CreateHead() {
LinkList head;
head= (LinkList)malloc(sizeof(LNode));
if (!head)
return ERROR;
//头结点的数据域存放链表的长度
head->data = 0;
head->next = NULL;
return head;
}
2、头插法创建链表:逆序创建链表
方法功能:由于采用头插法每插入一个结点后该结点就会成为下一个结点的直接前驱,因此如果采用正序插入的话形成的链表就会是链表就会是逆序的,故而采用逆序插入,此时的链表就会变为正序的
//用表头插入法逆序建立带头结点的单链表单链表
Status CreatLinkList1(LinkList L , int n, ElemType* E)
{
int i;
LinkList p;
//注意:插入过程是将数组的最后一个元素向前推依次插入,
//而最后得到的结果是顺序的
for (i = n - 1; i >= 0; --i) {
p = (LinkList)malloc(sizeof(LNode));
if (!p)
return ERROR;
p->data = E[i];
p->next = L->next;
L->next = p;
//修改链表的长度
L->data++;
}
return OK;
}
3、尾插法创建链表:正序插入结点
方法功能:由于尾插法,没插入一个结点,下一个几点就会变成当前结点的直接后继,故而只需正序插入结点即可实现链表也是正序的
Status CreatLinkList2(LinkList L, int n, ElemType* E)
{
int i;
LinkList p, r;
r = L;
for (i = 0; i < n; i++) {
if (!(p = (LinkList)malloc(sizeof(LNode))))
return ERROR;
p->data = E[i];
r->next = p;
r = p;
//修改链表的长度
L->data++;
}
r->next = NULL;
return OK;
}
4、使用交互式创建链表:按照用户输入数据的顺序插入结点
方法功能:根据输入的数据实现链表创建链表,方法需要用户需要创建的结点数
Status CreatLinkList3(LinkList L, int n)
{
int i;
LinkList p, q, s;
for (i = 0; i < n; i++) {
s = (LinkList)malloc(sizeof(LNode));
printf("请输入第%d个结点的值:", i + 1);
scanf_s("%d", &s->data);
p = L;
q = L->next;
while (q!=NULL)
{
p = q;
q = q->next;
}
s->next=NULL;
p->next = s;
}
return OK;
}
5、插入结点:
方法功能,根据用户指定的位置(i)之前插入结点(e)
Status InsertLinkLNode(LinkList L, int i, ElemType e)
{
LinkList r, p = L;
int k = 0;
while (p->next != NULL && k < i - 1) {
p = p->next;
k++;
}
//定位插入点失败
if (!p->next || k > i - 1)
return ERROR;
//开辟新结点
if (!(r = (LinkList)malloc(sizeof(LNode))))
return OVERFLOWW;
r->data = e;
r->next = p->next;
p->next = r;
L->data++;
return OK;
}
6、删除一;删除指定的位置(i),并由(e)带回删除的元数
方法功能;
1,移动指针使之指向指定的位置
2、判断输入的位置是否合法
3、删除元素,并将被删除的元素用e带回
Status Del_LinkLNode1(LinkList L, int i, ElemType*e)
{
LinkList delete, p = L;
int k = 0;
while (p->next != NULL && k < i - 1) {
p = p->next;
k++;
}
//无法删除
if (!p->next || k > i - 1)
return ERROR;
delete = p->next;
p->next = delete->next;
*e = delete->data;
free(delete);
return OK;
}
7、删除二:删除指定元素(e)在链表中第一次出现的元素
方法功能:
1、判断输入的数据元素是否在链表中出现
2、实现对结点的删除
Status Del_LinkLNode2(LinkList L, ElemType e)
{
LinkList p, q;
p = L;
q = L->next;
while (q->data!=e||q==NULL)
{
p = q;
q = q->next;
}
if (q == NULL)
return ERROR;
p->next = q->next;
free(q);
return OK;
}
8、删除三:删除指定元素(e)在链表中出现所有的元素
方法功能:
实现对出限e的所有元素都进行删除
Status Del_LinkLNode3(LinkList L, ElemType e)
{
LinkList p, q;
int tag = FLASE;
p = L;
q = L->next;
while (q!=NULL)
{
if (q->data == e) {
p->next = q->next;
free(q);
tag = TRUE;
}
else
{
p = q;
}
q=p->next;
}
return tag;
}
9、打印链表
方法功能:将链表正序输出
void PrintLinkList(LNode L) {
LinkList p = L.next;
while (p)
{
printf("%d->", p->data);
p = p->next;
}
printf("^\n");
}
10、释放链表
方法功能:实现对链表的开辟资源进行释放
void FreeLinkList(LinkList L) {
LinkList p, q;
p = L;
while (p!=NULL)
{
q = p;
p = p->next;
free(q);
}
L = NULL;
printf("链表销毁成功");
}
11、链表的归并(了解)
方法功能将有序链表A,B归并到链表C中,C依然有序
Status Mergesort(LinkList A, LinkList B, LinkList C) {
LinkList pa, pb,pc;
pa = A->next;
pb = B->next;
pc=C ;
//通过while循环对两个链表的元素进行比对,结束条件是否有链表的元素已遍历完
while (pa&&pb)
{
//若A表结点的值更小取A表的结点到C中
if (pa->data <= pb->data) {
pc->next = pa;
pc=pa;
pa = pa->next;
}
else
{//否则将B表的结点移动到C表表尾
pc->next = pb;
pc = pb;
pb = pb->next;
}
}
//将A表或者B表中剩余的元素移至C表表未
if (pa) {
pc->next = pa;
}
else {
pc->next = pb;
}
PrintLinkList(*C);
return OK;
}
代码调试
测试代码:
1、测试链表的创建(此处只测试采用头插法,剩余的两种方法,可个人测试)
ElemType E[] = {1,2,3,4,3};
int e;
LinkList head=CreateHead();
//测试用头插法创建链表
Status status = CreatLinkList1(head, 3, E);
//测试用尾插法创建链表
//Status status = CreatLinkList2(head, 5, E);
//测试交互方法创建链表
//Status status = CreatLinkList3(head, 5);
if (!status) {
printf("创建失败\n");
return 0;
}
else
{
printf("创建成功,链表状态如下:");
PrintLinkList(*head);
}
测试结果:
2、测试插入和删除(删除只测试其中一个,其余两个,个人测试):
//测试插入
status = InsertLinkLNode(head, 3, 19);
PrintLinkList(*head);
//测试删除一
status = Del_LinkLNode1(head, 3, &e);
//测试删除二
//status = Del_LinkLNode2(head, 5);
//测试删除三
//status = Del_LinkLNode3(head, 3);
if (status) {
printf("删除成功\n");
printf("删除的元素是:%d\n", e);
}
else
printf("删除失败");
PrintLinkList(*head);
测试结果:
4、测试链表的归并
//测试归并
LinkList A, B, C;
int a[] = { 1,2,3,4,5 };
int b[] = { 10,11,12,13,14 };
Status status = 0;
A = C = CreateHead();
B = CreateHead();
status=CreatLinkList1(A, 5, a);
printf("链表A的状态\n");
PrintLinkList(*A);
status=CreatLinkList1(B, 5, b);
printf("链表B的状态\n");
PrintLinkList(*B);
status=Mergesort(A, B, C);
if (!status) {
printf("归并失败\n");
return 0;
}
else
{
printf("归并成功,链表状态如下:");
PrintLinkList(*C);
}
测试结果
小小的总结
以上便是本期的内容,其中包括链表的基本概念的介绍(可以简单了解一下我的个人看法和基本概念的总结,不是很细节,毕竟还有许多大佬的总结和书籍总结的更好),实现了对链表的几个基本操作(对链表的操作最重要的是理解链接关系和链表的改接等,代码只是对你想法的实现),要脚踏两只船(可能不是很恰当,词穷了。。。),既要训练逻辑思维,也要提高代码的编写能力。由于个人能力有限,如有出错或者更优秀的算法,可以交流探讨。。。。