实验二 链表及其应用
一 实验目的
(1)熟练掌握单链表的插入、删除、查找定位等基本算法;
(2)掌握循环链表的概念及其基本算法;
(3)能利用单链表解决简单的问题,如一元多项式的运算。
二 实验原理
课堂笔记中关于链表的全部内容中。(不包括算法)
1.链式储存结构
struct node
{
elemtype data;
Struct node *next;
};
typedef struct node Lnode,*Linklist;
2.说明
(1)采用链式储存结构的线性表称为链表。
(2)struct node 用来定义一个数据元素,链表中的一个数据元素称为一个结点。
(3)每个结点由两部分组成:
data:用来存储结点自身信息,称为数据域。
next:用来存储当前结点直接后继的地址。(指针域) NULL和null表空指针。
3.算法(略)
4.双向链表,循环链表。
5.总结
(1)单链表是为单个数据建立的储存结构。
(2)储存密度低。(节省储存空间)
(3)查找对象不方便。
(4)插入和删除结点不需要移动其他结点,插入和删除效率比较高。
(5)动态储存malloc开辟储存空间
(6)储存结构(物理结构)与逻辑结构(次序)不一致,通过结点的next指针访问或查找结点逻辑次序。
三 实验内容
已知一单链表l中的存储的数据元素的类型为整型。
1 设计算法,建立一个带有空的头结点的单链表,空表。
2 设计算法,向单链表中插入元素。可以采用头插法和尾插法两个方法中任何一种。以-1作为输入元素结束信号。
3 遍历单链表中结点。
4 求链表长度*/
5 按位序查找结点指针*/
6 按位序查找元素值*/
7 按值查找结点位序*/
8 在链表中第i个位置插入元素
9 按值删除节点*/
10 按位序删除节点*/
11查找单链表最大值*/
12查找单链表最小值*/
13 单链表链接
四、实验源代码
#define _CRT_SECURE_NO_WARNINGS 1
/*实验二 链表及其应用*/
#include /* stdlib.h包括函数malloc()、free()等等。*/
#include
typedef int ElemType; /* 定义链表中数据元素类型ElemType,int类型 */
#define TRUE 1
#define FALSE 0
#define flag -1
typedef struct node { //单链表的结点类型
ElemType data; //数据域
struct node* next; //指针域
} LNode, * LinkList; //LNode是此结构体的类型名,而LinkList是结构体指针的类型名
LinkList Init_LinkList()/* 初始化单链表,建立一个只有空的头结点的单链表 */
{
LinkList H = (LinkList)malloc(sizeof(LNode)); //为头结点分配内存
H->next=NULL; //头结点的指针域为空,即头结点没有后继结点
return H;
}
void Create_LinkList1(LinkList H) /*头插法建立单链表*/
{
LNode* s; //指向单链表结点的指针s
ElemType x; //待插入的数据元素值x
printf("请输入单链表结点的内容,以-1为结束标志:\n");//依次输入结点的值
scanf("%d", &x);
while (x != flag)
{
s=(LinkList)malloc(sizeof(LNode)); //为s指向的结点分配内存
s->data=x; //设置s指向的结点的数据域为x
s->next=H->next; //s结点的后继结点是头结点的后继结点
H->next=s; //头结点的后继结点是S结点
scanf("%d", &x);
}
}//后创建的排在前面,先创建的排在后面
void Create_LinkList2(LinkList H) /*尾插法建立单链表*/
{
LNode* s, * r = H; //s指向待插入的结点,r为尾指针指向尾结点
ElemType x; //待插入的数据元素值x
printf("请输入单链表结点的内容,以-1为结束标志:\n");//依次输入结点的值
scanf("%d", &x);
while (x != flag)
{
s = (LinkList)malloc(sizeof(LNode)); //为s指向的结点分配内存
s->data=x; //设置s指向的结点的数据域为x
s->next=NULL; //s结点的后继结点为空值
r->next=s; //r结点的后继结点为s结点
r=s; //尾指针指向新插入进来的s结点rear(尾部的)
scanf("%d", &x);
}
}//先创建的在前面,后创建的后面
void Traverse_LinkList(LinkList H) /* 遍历单链表 */
{
LinkList p; //遍历是将单链表中的所有元素都依次访问一次,即从前向后依次打印结点内容
p=H->next; //p指向首元素结点--p指向的是空的头节点--无论是头插还是尾插H本身都是不变的仍然为空的头节点
while (p!=NULL) //当p结点存在
{
printf("%d ", p->data); //输出p结点的值
p=p->next; //p指针后移
}
printf("\n");
}
int Length_LinkList(LinkList H) /*求链表长度*/
{
LinkList p=H; //p指向首元素结点
int n = 0; //结点计数器j归零
while (p!=NULL) //当p结点存在
{
n++; //计数器增1
p=p->next; //指针后移
}
return n-1;
}
LinkList Get_LinkList(LinkList H, int k) /*按位序查找结点指针,返回待找结点地址*/
{
LNode *p=H; //p指向头结点
int n = 0; //结点计数器归零
while (p->next!=NULL&&n<k ) //当p结点的后继结点存在,且计数器未达到指定位序k--促成立时n为k
{
n++; //计数器+1
p=p->next; //指针后移
}
if (n==k) return p; //当计数器达到位序要求,返回指向第k个结点的指针p||p->next!=NULL
else return NULL;
}//空的头结点为位序1
ElemType Get_LinkList1(LinkList H, int k) /*按位序查找结点,返回待找结点元素值*/
{
LNode* p = H;
int n = 0;//指向空节点--1指向第一个元素结点
while (p!=NULL && n<k)//结点存在并且计数器没有到达k(0-k)--k+1次--第k个元素结点
{
n++;
p=p->next;
}
if (p!=NULL)
return p->data;//查找成功,返回待找结点值
else return FALSE;
}
LinkList Get_LinkList2(LinkList H, ElemType x) /*按值查找结点,返回待找结点地址*/
{
LNode* p = H; //p指向首元素结点
while (p!=NULL) //当p结点存在
{
if (p->data== x)//判断p结点的值是否等于x
break;
p=p->next; //指针后移
}
if (p != NULL) return p;
else
{
printf("链表中不存在值为%d的元素\n", x);
return NULL;
}
}
int Get_LinkList3(LinkList H, ElemType x) /*按值查找结点,返回待找结点位序*/
{
LNode* p = H; //p指向首元素结点
int n = 1; //设置函数返回位序的初始值为1
while (p!=NULL && p->data!=x) //当p结点存在且p结点的值不等于待查找值x
{
n++;
p=p->next;
} //指针后移,位序+1
if (p->data==x) return n-1;
else return FALSE;
}//空的头结点为次序0
int Insert_LinkList1(LinkList H, int i, ElemType x) //在链表中第i个位置插入元素--类似头插法//不算头结点的位序
{
LNode* p, * s;
p = Get_LinkList( H, i-1); //定位p到i-1个结点,直接调用算法完成
if (p == NULL)
{
printf("插入位置溢出,插入失败!\n");
return FALSE;
}
else
{
s = (LinkList)malloc(sizeof(LNode)); //为s结点分配空间
s->data=x; //将x写入s结点的数据域
s->next=p->next; //设置s结点的后继结点为p结点的后继结点
p->next=s; //设置p结点的后继为s结点
return TRUE;
}
}
int Del_LinkList1(LinkList H, ElemType x) /*按值删除节点*/
{
LinkList p, q;//结点p始终作为q结点的直接前驱,同时向后移动位置,直到找到q结点的值等于x
p = H; //设置p
q = p->next; //设置q
while (q!=NULL && q->data!=x) //当q结点存在且q结点的值不等于x
{
p=p->next; //p结点后移
q=q->next; //q结点后移
}
if (q==NULL)
{
printf("链表中不存在值为%d的结点\n", x);
return FALSE;
}
else
{
p->next=q->next; //从单链表中踢出q结点,即设置p结点的后继为q结点的后继
free(q); //释放q结点
return TRUE;
}
}//删除节点的关键是找到待删除元素之前的节点
int Del_LinkList2(LinkList H, int i) /*按位序删除节点*/
{
LinkList p, q;
p = Get_LinkList(H, i - 1); //定位p到第i-1个结点,直接调用算法完成。
if (p == NULL)
{
printf("第%d个结点不存在\n", i - 1); return FALSE;//空节点也是节点
}
else
if (p->next == NULL)
{
printf("第%d个结点不存在\n", i); return FALSE;
}
else
{
q=p->next; //定位q到待删除结点
p->next=q->next; //从单链表中踢出q
free(q); //释放q结点
return TRUE;
}
}
ElemType Max_LinkList(LinkList H) /*查找单链表最大值*/
{
LinkList p;
ElemType max;
p=H; //p指向首元素结点
max=p->data; //设置首元素结点的值为最大值
p=p->next; //为下一次比较做准备,p指针后移
while (p!=NULL) //当p结点存在
{
if (p->data>max) //max和p结点的值比较
max=p->data; //刷新x的值
p=p->next; //p指针后移
}
return max;
}
ElemType Min_LinkList(LinkList H) /*查找单链表最小值*/
{
LinkList p;
ElemType min;
p=H->next; //p指向首元素结点(必须是元素结点)
min=p->data; //设置首元素结点的值为最小值
p=p->next; //为下一次比较做准备,p指针后移
while (p!=NULL) //当p结点存在
{
if (p->data<min) //p结点的值和当前最小值min比较
min=p->data; //刷新x的值
p=p->next; //p指针后移
}
return min;
}
void Cat_LinkList(LinkList H1,LinkList H2)//单链表连接,把单链表H2的结点依次连接在单链表H1后面
{
while (H1->next != NULL)
{
H1 = H1->next;
}
H1->next = H2->next;
}
int main()
{
int i, j;
char ch;
ElemType e;
LinkList L,L2;
ElemType x;
printf("**************************************************\n");
printf(" 单 链 表 常 用 算 法\n");
printf("**************************************************\n\n");
printf("1、初始化单链表:设置其为空表\n");
L = Init_LinkList();
if (L) printf("单链表初始化成功……\n\n");
printf("2、创建单链表:\n");
do
{
fflush(stdin);
printf("请选择头插法(T)还是尾插法(W): ");
scanf("%c", &ch);
} while (ch != 'T' && ch != 't' && ch != 'W' && ch != 'w');
if (ch == 'T' || ch == 't')
{
printf("您选择的是头插法\n");
Create_LinkList1(L);
}
else
{
printf("您选择的是尾插法\n");
Create_LinkList2(L);
}
printf("单链表创建成功……\n\n");
printf("3、遍历单链表:\n"); /*依次访问单链表中所有元素*/
Traverse_LinkList(L);
printf("4、单链表长度为:%d\n\n", Length_LinkList(L));
printf("5、单链表的插入操作:\n");
printf("请输入待插入的位序(location):");
scanf("%d", &i);
printf("请输入待插入的数据(data):");
scanf("%d", &e);
if (Insert_LinkList1(L, i, e)) printf("插入操作执行成功……\n操作结果:");
Traverse_LinkList(L);
printf("\n");
printf("6、单链表的删除操作:\n");
do
{
printf("请选择按值删除(Z)还是按位序删除(X): ");
scanf("%c", &ch);
} while (ch != 'Z' && ch != 'z' && ch != 'X' && ch != 'x');
if (ch == 'Z' || ch == 'z')
{
printf("请输入待删除的数据元素值:");
scanf("%d", &x);
if (Del_LinkList1(L, x)) printf("删除操作执行成功……\n操作结果:");
else printf("删除操作未执行成功……\n操作结果:");
}
if (ch == 'X' || ch == 'x')
{
printf("请输入待删除的数据的位序:");
scanf("%d", &e);
if (Del_LinkList2(L, e)) printf("删除操作执行成功……\n操作结果:");
else printf("删除操作失败……\n操作结果:");
}
Traverse_LinkList(L);
printf("\n");
printf("7、单链表的查找操作:\n");
do
{
printf("请选择按值查找(Z)还是按位序查找(X): ");
scanf("%c", &ch);
} while (ch != 'Z' && ch != 'z' && ch != 'X' && ch != 'x');
if (ch == 'Z' || ch == 'z')
{
printf("请输入待查找的数据元素值:");
scanf("%d", &x);
if (Get_LinkList3(L, x)) printf("待查找的值为%d数据元素的位序为:%d\n\n", x, Get_LinkList3(L, x));
else printf("待查找的值为%d数据元素不存在\n\n", x);
}
if (ch == 'X' || ch == 'x')
{
do
{
printf("请输入待查找的数据元素的位序(1~%d):", Length_LinkList(L));
scanf("%d", &i);
} while (i<1 || i> Length_LinkList(L));
x = Get_LinkList1(L, i);
printf("待查找的位序为%d数据元素值为:%d \n\n", i, x);
}
printf("8、单链表的最大值是:");
printf("%d\n\n", Max_LinkList(L));
printf("9、单链表的最小值是:");
printf("%d\n\n", Min_LinkList(L));
printf("10、单链表的连接:");
printf("初始化单链表L2:设置其为空表\n");
L2=Init_LinkList();
if(L2) printf("单链表初始化成功……\n\n");
printf("2、创建单链表:\n");
do
{
fflush(stdin);
printf("请选择头插法(T)还是尾插法(W): ");
scanf("%c",&ch);
}while(ch!='T' && ch!='t' && ch!='W' && ch!='w');
if(ch=='T' || ch=='t')
{
printf("您选择的是头插法\n");
Create_LinkList1(L2);}
else
{
printf("您选择的是尾插法\n");
Create_LinkList2(L2);
}
printf("单链表创建成功……\n");
Cat_LinkList(L,L2);
printf("单链表连接成功,操作结果是:");
Traverse_LinkList(L);
return 0;
}
六、实验总结
1.本实验中头结点的序号为0。
2.头插法的存储顺序和逻辑顺序(顺序储存)相反,而尾插法的储存顺序和逻辑顺序则相同(顺序储存)。
3.NULL在实际理解时可以把它当做一个什么都没有但存在的空节点。
4.返回序号或者利用序号时要注意循环结束的条件。
5.判断条件何时是p!=NULL何时是p->next!=NULL也需要注意。
6.注意删除结点的实现。
7.求最大,最小值时需要用链表中的data更新max和min的值。(注意不要用空链表的data值更新min值)