《数据结构》实验指导书
来源: 发布时间:2006-5-29 16:53:37 点击:
 实验一:单链表的插入和删除
实验目的:
单链表的数据域是字符串,且不允许重复的串插入表中,删除操作是根据输入的字符串,先找到相应的结点,后删除之。
预习要求:
 要求熟悉并且掌握单链表的建表、插入、查找和删除算法以及单链表数据结构的实现等。
实验步骤:
下面给出单链表的建表、插入、查找和删除算法以及单链表的数据结构,请学员根据下面给出的算法编程,完成这次实验。
1.链表的建立 
在线性链表上进行的任何操作,其前提是必须建立这条链。建立链表我们可以利用C语言中所提供的标准函数来实现。
链表的结点结构可以定义如下:
typedef struct node
{
elemtype data; /* elemtype可以是任何类型 */
struct node * next;
}sInode;
下面给出建立n个结点的线性链表的算法:
void creatlink(sInode head,int n)
{
sInode *p,*q;
int i;
if (n==0)
return;
head=(sInode*) malloc(sizeof(sInode));
p=head;
scanf("%c",&p->data);
for (i=2;i<=n;i++)
{
q=(sInode*)malloc(sizeof(sInode));
p->next=q;
p=q;
scanf("%c",&p->data);
}
p->next=NULL;

2.链表的插入
一般地,链表结构实现的手稿操作所插位置可能有以下四种情况: (设head为头指针)
1)原来的链表是空表,则插入结点应作为表头结点,其操作:
head=x,x->next=NULL
2)在链表中第一个结点位置之前插入,则插入结点应作为新的表头结点,其操作:
x->next=head,head=x
3)在链表中间某个位置插入,在q结点之后,p结点之前,其操作:
q->next=x;x->next=p
4)如果链表中根本不存在所指定的结点,则将新结点在链表尾插入。其操作:
x->next=p->next,p->next=x; 或 p->next=x,x->next=NULL
在插入操作中需要确定插入的位置,因此常常包含查询操作,即在链表中查找数据域中关键值为key的结点,然后实施插入运算,要根据上面假设的四种情况,下面给出线性表的插入算法:
void inserlist(head,key,b)
sInode *head;
elemtype key,b;
/* 在head为入口的链表中,在与key值相同结点之前插入数值b的结点 */
{
sInode *x,*p,*q;
x=(sInode *) malloc(sizeof(sInode));
x->data=b;/* 建立新结点X,数值为b */
if(head==NULL)
{
head=x;
x->next=NULL; /* 链表空,新结点作为链表唯一一个结点心*/
}
else
{
if (head->data==key)
{
x->next=head;
head=x; /* 插入在链表第一个结点之前,插入结为新的表头 */
}
else
{
p=head;
while(p->data!=key&&p->next!=NULL)
{
/* 循环查找,q是p的前趋结点心 */
q=p;
p=p->next;
}
if (p->data=key)
{
q->next=x;
x->next=p; /* 插入在表末,新结点作为新的表尾 */
}
}
}
}
该算法的循环查询中设置变量q是一旦访问到p->data=key的存储结点p的前趋地点,从而能实现在q和p之间插入。 
3.链表的查找 
在链表的操作中,往往只知道某一结点的关键值,因此需要确定该结点的地址,因为一旦结点的地址确定,那么就能知道该结点的全部信息;而且还能通过链指针找到该结点后继结点的情况,这对结点的插入和删除都是非常必要的。因此,只有通过查找才能确定所需要结点的地址,则,第一,查找是否成功,即被查结点是否在链表中;第二,经过查找,得到查找结点的地址和该结点的前趋结点地址。下面就来介绍链表的查找。
设线性链表的数值是有序的,且从小到大排列,链表入口为head,客观存在查找结点的关键值为key。通过查找可确定该结点是否链表中,若在链中,则显示“查找成功”,不在链表中,则显示“查找失败”。若查找成功,就能得到被查找结点的地址和前趋结点地址。下面给出线性链表查找的算法:
void searchli(head,key)
sInode * head;
elemtype key;
{
sInode *p,*q;
p=head;
q=p;
while(p!=NULL&&p->data<key)
{
q=p;
p=p->next;
}
if(p->data==key&&p!=NULL)
{
printf("查找成功");
return;
}
else
printf("查找失败");
}
该算法若查找成功,p是关键值为key结点的地址,q是p的前趋结点的地址。 若被查找链表的数据是无序的,则可修改while语句中的条件,去掉(p->data<key),改为(p->data!=key)。这样查找的效率就大大降低。原因很简单,无序的链表,一旦查找失败,必须查遍全表,而有序链表中就不必这样,如有n个结点链表,其查找的平均概率为n/2。
4.链表的删除 
链表的删除操作,需要从头到到尾查找链表以找到数据域值等x的结点,若查找到指定的结点,则删除该结点后,只要将其前趋结点的指针域修改为指向被删除结点的后继结点即可;同时,把被删除的结点送还空间,以备再次使用。若查找不到指定结点,则给出提示信息。
  删除某一结点,则必须知道被删除结点的前趋结点。设p指向被删除结点,q指向p的前趋结点,则删除的基本操作步骤为
q->next=p->next
  同时为了充分利用被删除结点的空间,可用C语言中的标准函数free()来释放空间。
  下面讨论被删除结点在链表中的位置有四种删除的情况。
1)链表只有一个结点,该结点就是被删除的结点,其操作:
  head=NULL 或 head=head->next
2)被删除的结点是链中第一个结点,其操作:
  head=head->next
3)被删除的结点在链表的中间,其中删除结点为p,前趋结点为p,其操作:
  q->next=p->next
4)被删除的结点在链尾,其中删除结点为p,前趋结点为q,其操作:
  q->next=NULL 或 q->next=p->next
以上四种操作中,不难看出第一、第二种情况的操作可以用head=head->next表示,而第三、第四种情况可以用q->next=p->next来表示。下面给出链表删除的完整算法:
void deletelink(head,key)
sInode * head;
elemtype key;
/* 在head为入口的链表中删除值为key的结点 */
{
sInode *p,*q;
p=head;
q=p;/* 设置前趋结点 */
while(p!=NULL&&p->data!=key) /* 查找删除结点的位置 */
{
q=p;
p=p->next;
}
if(head==NULL)
{
printf("无此结点");
return;
}
else
if (head==p)
head=p->next;/* 删除结点 */
else
q->next=p->next; /* 删除链中间或链尾结点 */
free(p);/* 释放空间单元 */
}
  对于长度为n 的链表,删除的主要时间是花费在查找数据域值为指定的关键值key的结点上。假若在线性表各位置上的删除概率相等,则查找时的平均比较次数为:
(1+2+……+n)/n=(n+1)/2
而每次的比较时间是一个常数,所以插入和删除的时间复杂度为O(n)。 
实验内容:
1. 单链表的连接:设la,lb为两个数据域为整数的单链表的头结点,且其中的数据都是递增有序的,现在要求将两个单链表连接在一起,连接后的单链表以lc为头结点,且其中的数据是递减有序的(允许有相同的元素出现)。(设la中的数据元素个数为10个(0,2,4,6,8,10,12,14,16,18),lb中的数据元素个数为10个(0,3,6,9,12,15,18,21,24,27)。)
2. 单链表的交集:设la,lb为两个数据域为整数的单链表的头结点,且其中的数据都是递增有序的,现在要求这两个单链表的交集,将结果保存在以lc为头结点的单链表中。(设la 中的数据元素个数为10个(1,3,5,7,9,11,13,15,17,19),lb中的数据元素个数为10个(0,3,6,9,12,15,18,21,24,27))
因为两个实验是在同一种数据结构上操作的,所以可以将两者合并成一个程序,并分别用两个函数来实现上述两个操作。
//main.cpp   主程序
#include "node.h"   //包含单链表数据结构的头文件

void exercise1(void)   //实现实验1的函数

本新闻共5页,当前在第1页  1  2  3  4  5