目录
(1)根据输入的一系列整数,以0标志结束,用头插法建立单链表,并输出单链表中各元素值,观察输入的内容与输出的内容是否一致。
(2)在单链表的第i个元素之前插入一个值为x的元素,并输出插入后的单链表中各元素值。
(3)删除单链表中第i个元素,并输出删除后的单链表中各元素值。
(4)在单链表中查找第i个元素,如果查找成功,则显示该元素的值,否则显示该元素不存在。
4.设计一个算法,将链表中所有结点的链接方向“原地”逆转,即要求仅利用原表的存储空间,换句话说,要求算法的空间复杂度为O(1)。
实验一 :将递增有序的链表A和B合并为递减有序的链表C,且使用原空间
实验二 :头插法进行创建链表,观察输出、在链表i位置中插入值为x的元素,输出链表的值、删除i位置的元素 ,输出链表的值、查找第i个元素进行相关反馈输出
实验三:将链表A中数据小于0和大于0的数据分别存放至B、C表中,且要求利用A表原节点空间
一 实验目的
1.熟练掌握线性表的顺序存储和链式存储结构。
2.熟练掌握线性表的相关典型操作。
二 实验内容及要求
实验内容:
-
假设有两个按元素值递增有序排列的线性表A和B,均以单链表作为存储结构,请编写算法将A表和B表归并成一个按元素值递减有序(即非递增有序,允许表中含有值相同的元素)排列的线性表C,并要求利用原表(即A表和B表)的结点空间构造C表。
-
编程实现如下功能:
(1)根据输入的一系列整数,以0标志结束,用头插法建立单链表,并输出单链表中各元素值,观察输入的内容与输出的内容是否一致。
(2)在单链表的第i个元素之前插入一个值为x的元素,并输出插入后的单链表中各元素值。
(3)删除单链表中第i个元素,并输出删除后的单链表中各元素值。
(4)在单链表中查找第i个元素,如果查找成功,则显示该元素的值,否则显示该元素不存在。
3.设计算法将一个带头结点的单链表A分解为两个具有相同结构的链表B和C,其中B表的结点为A表中值小于零的结点,而C表的结点为A表中值大于零的结点(链表A中的元素为非零整数,要求B和C表利用A表的结点)。
4.设计一个算法,将链表中所有结点的链接方向“原地”逆转,即要求仅利用原表的存储空间,换句话说,要求算法的空间复杂度为O(1)。
实验要求:
1.键盘输入数据;
2.屏幕输出运行结果。
3.要求记录实验源代码及运行结果。
4.运行环境:CodeBlocks/Dev c++/VC6.0等C编译环境
三 实验过程及运行结果
实验一 :将递增有序的链表A和B合并为递减有序的链表C,且使用原空间
一 算法设计思路
四个函数分别为:InputData、Output、MergeLinkList、ReverseLinkList。
InputData函数用于新建链表节点并连接,同时将数据储存在链表节点的指针域,最后将尾节点的指针域设为NULL,作为Output函数输出时停止循环的条件。同时根据输入的个数记录链表中的数据个数,用于作为ReverseLinkList函数中的参数
Output函数将链表的元素逐个输出,在p!=NULL时向下移动,输出当前节点数据,直到NULL时停止输出。
MergeLinkList函数用于将储存在A和B表中的数据按照递增顺序进行排列,同时将A表和B表中的数据所在节点按照递增顺序连接成一条链表C中(此时C是递增有序),其中要考虑的情况是某一个开始连续节点都小于另外一个表的前一个结点,小于前一个表的后一个节点的情况,此时需要插入一串子链表。另外一种情况是只某一个节点小于另外一个表前一个结点大于后一个节点,只需插入一个节点。如果是最后一个节点则需要判断与主链表的倒数第一个和倒数第二个节点数据的判断,同时主链表指针需要指向倒数第二个数据节点再进行插入,或下移一位将其插入至链表最后。
ReverseLinkList函数用于将递增有序的链表C反转变为递减有序的链表,因为C链表需要用A和B表的节点,因此新建与数据节点数相等的数组,用于储存数据,先遍历链表C,将数据储存在数组中,再逆序将数组中的数据依次储存在C表中就完成了数组的反转。
Main函数中先创建链表A、B头节点,在进行InputData数据输入储存完成之后,用MergeLinkList函数进行合并递增排序,再通过ReverseLinkList函数对链表进行翻转。
二 源程序代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <iostream>
using namespace std;
/*test 1
1. 假设有两个按元素值递增有序排列的线性表A和B,均以单链表作为存储结构,请编写算法将A表和B表归并成一个按元素值递减有序(即非递增有序,允许表中含有值相同的元素)排列的线性表C,并要求利用原表(即A表和B表)的结点空间构造C表。
A 1 3 5 7 9
B 2 4 6 8 10
C 10 9 8 7 6 5 4 3 2 1
*/
typedef struct LinkList
{
int data;
LinkList* next;
}*LNode;
void InputData(int n, LinkList &l)//此处要加&
{
//指向头结点
LNode p = &l;
for (int i = 0; i < n; i++)
{
LNode newLnode=new LinkList;//建一个新节点
p->next = newLnode;
printf("请输入第%d个数据\n",i + 1);
//p->next = ;//新建下一个节点
scanf("%d", &p->next->data);//输入新建节点数据
p = p->next;//指针指向
}
p->next = NULL;
}
void Output(int n, LinkList&l)
{
LNode p = &l;
p = p->next;//指向头节点下一个
for (int i = 0; i < n; i++)
{
printf("%d ", p->data);
p = p->next;
}
}
void MergeLinkList(LinkList &a, LinkList &b, int i,int j)//先按递增顺序排列链表
{
LNode p = &a;
LNode s = &b;
LNode end = NULL;
//将s和p都跳到下一个节点
p = p ->next;
s = s->next;
while (1)
{
LNode tem = NULL;
if (p->next == NULL || s->next == NULL)
{
end = p;
break;
}
if (s->data >= p->data )//如果大于某个
{
if (s->data <=p->next->data)//且小于后一个 则插入
{
tem = s->next;
s->next = p->next;
p->next = s;
s = tem;
}
else//否则p后移
{
p = p->next;
continue;
}
}
else//说明s所指数据小于p所指,插到p前面
{
tem = s->next->next;
s->next = p;
s = tem;
}
}//全部弄完之后还剩s最后一个元素
//对最后一个元素进行操作
if (s->data >= p->data)//一串都大于
{
while (p->next != NULL)
p = p->next;
if (s->next == NULL&&s->data<=p->data)
{
LNode ak = &a;
while (ak->next != p)
ak = ak->next;//ak定位到倒数第二位
s->next = ak->next;
ak->next = s;
}
else
{
p->next = s;
while (p->next != NULL)
p = p->next;
p->next = NULL;
}
}
}
void ReverseLinkList(LinkList& a, int sum)
{
//用b?返回b?
//数组记录数据 重新对链表赋值 同时记录长度
LNode p = &a;
p = p->next;
LNode l = &a;
LNode r = NULL;
int count = 0;
int* c = (int*)malloc(sizeof(int) * sum);
while (p != NULL)
{
c[count] = p->data;
p = p->next;
count++;
}
while (count!=0)
{
l = l->next;
l->data= c[count-1];
count--;
}
}
int main()
{
LinkList a;
LinkList b;
a.next = NULL;
b.next = NULL;
int n = 0, m = 0;
printf("请输入链表A的元素个数:\n");
scanf("%d", &n);
printf("请输入链表B的元素个数:\n");
scanf("%d", &m);
InputData(n, a);
InputData(m, b);
MergeLinkList(a, b, n, m);
ReverseLinkList(a, n+m);
printf("递减排序后的表C如下:\n");
Output(n+m, a);
return 0;
}
实验二 :头插法进行创建链表,观察输出、在链表i位置中插入值为x的元素,输出链表的值、删除i位置的元素 ,输出链表的值、查找第i个元素进行相关反馈输出
一 算法设计思路
包含五个函数分别是:OutputLink、HeadInertLink、LocInsertEle、LocDelEle、LocSearch
OutputLink函数将链表的元素逐个输出,在p!=NULL时向下移动,输出当前节点数据,直到NULL时停止输出。
HeadInertLink,头插法建立链表,入的头节点,将新插入的节点指针域指向头节点原指针域所指向位置,再建立新节点插入到头节点之后,最后将数据域赋值后输入数据进行下一次循环的判断,最后调用OutputLink将链表数值输出
LocInsertEle,根据传入的数值i,循环i-1次,将p定位在要插入位置的上一个,如果插入位置错误(超出链表范围,或为负值)则会做出相应提示并退出程序,若完成正常插入则调用OutputLink将链表数值输出。
LocDelEle根据传入的数值i,循环i-1次,将p定位在要插入位置的上一个,如果删除位置错误(超出链表范围,或为负值)则会做出相应提示并退出程序,若完成正常删除则调用OutputLink将链表数值输出。
LocSearch通过传入的位置i进行i-1次循环,如果存在则输出p->next->data,如果不存在则做出相应提示
Main函数先创建头节点,调用HeadInertLink进行头插创建链表,然后调用LocInsertEle 将999插入至第五位,然后调用LocDelEle将第五位元素删除,调用LocSearch分别查找第三位第11位元素进行观察输出。
二 源程序代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <iostream>
using namespace std;
typedef struct LinkList
{
int data;
LinkList* next;
}*LNode;
void OutputLink(LinkList& a)
{
LNode p = &a;
p = p->next;
while (p != NULL)
{
printf("%d ", p->data);
p = p->next;
}
printf("\n");
}
void HeadInertLink(LinkList&a)//前插法 插入以0作为结尾
{
int data;
LNode p = &a;
int count = 1; LNode tem = NULL;
printf("请输入第%d个要储存的数据:\n",count);
scanf("%d", &data);
while (data != 0)
{
LNode node=new LinkList;//新节点
node->next = a.next;
a.next = node;//前插
node->data = data;
count++;
printf("请输入第%d个要储存的数据:\n",count);
scanf("%d", &data);
}
printf("操作成功,目前链表数据为:");
OutputLink(a);
}
void LocInsertEle(int Element, LinkList& a, int Loc)//分别代表元素,链表,和插入位置
{
if (Loc <= 0)
{
printf("位置错误,无法插入"); exit(0);
}
//if(Loc<=0||Loc>maxsize)
int count = 1;
LNode node = new LinkList;
node->data = Element;
LNode p = &a;
for (int i = 0; i < Loc-1; i++)
{
if (!p)
{
printf("位置错误,无法插入");
exit(0);
}
p = p->next;
}
node->next = p->next;
p->next = node;
printf("插入操作成功,目前链表数据为:");
OutputLink(a);
}
void LocDelEle(int Loc, LinkList& a)
{
if (Loc <= 0)
{
printf("位置错误,无法删除"); exit(0);
}
LNode p = &a;
for (int i = 0; i < Loc-1; i++)
{
if (!p)
{
printf("位置错误,无法删除");
exit(0);
}
p = p->next;
}
p->next = p->next->next;
printf("删除操作成功,目前链表数据为:");
OutputLink(a);
}
void LocSearch(LinkList&a,int Loc)
{
LNode p = &a;
for (int i = 0; i < Loc - 1; i++)
{
if (p->next == NULL)
{
printf("该元素不存在");
exit(0);
}
p = p->next;
}
printf("要查找位置的元素是%d \n", p->next->data);
}
int main()
{
LinkList a;
a.next = NULL;
HeadInertLink(a);
LocInsertEle(999, a, 5);
LocDelEle(5,a);
LocSearch(a, 3);
LocSearch(a, 11);
return 0;
}
实验三:将链表A中数据小于0和大于0的数据分别存放至B、C表中,且要求利用A表原节点空间
一 算法设计思路
两个函数分别是OutputLink、SplitLink
OutputLink:这个函数的目的是遍历一个链表并输出其中的元素。首先,函数接收一个的头节点后,创建一个指向链表头部的指针p。接下来,函数通过p = p->next;将p移动到链表的下一个节点。while循环,打印出当前节点的数据, p移动到下一个节点。这个过程会一直持续到链表的末尾,即p变为空。
SplitLink:它接受四个参数:一个链表a、两个节点指针b和c以及一个整数count。该函数的主要目的是将链表a分割成两部分,其中一部分包含所有小于0的元素,另一部分包含所有大于0的元素。根据count的值,函数会以不同的方式处理链表的头结点:如果count小于0,则表示第一个节点是B链表的头结点,第二个节点是C链表的头结点。在这种情况下,函数会遍历链表a,将所有小于0的元素插入到B链表的末尾,并将所有大于0的元素插入到A链表的末尾。如果count大于0,则表示第一个节点是C链表的头结点,第二个节点是B链表的头结点。在这种情况下,函数会遍历链表a,将所有大于0的元素插入到C链表的末尾,并将所有小于0的元素插入到A链表的末尾。在函数内部,变量s用于指向当前正在处理的节点,而变量cur用于遍历链表。此外,变量p用于指向B链表的头结点,而变量head用于存储C链表的头结点。
Main: 程序首先创建一个空的头节点a,然后通过循环读取用户输入的数据,并将数据插入到链表a中。接下来,程序根据链表a的第一个节点的值来确定B链表和C链表的头结点。最后,程序调用SplitLink函数将链表a分割成两个链表,并通过OutputLink输出结果。
二 源程序代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <iostream>
using namespace std;
typedef struct LinkList
{
int data;
LinkList *next;
}*LNode;
void OutputLink(LinkList a)
{
LNode p = &a;
p = p->next;
while (p)
{
printf("%d ", p->data);
p = p->next;
}
puts("");
}
void SplitLink(LinkList& a, LNode b, LNode c,int count)
{
LNode s = &a;
LNode cur = &a;
LNode p = &a; //p指向B s指向C链表移动 cur指向主链表
LNode head=p->next;
p = head;
cur = cur->next->next;
if (count < 0)//说明第一个节点是Bhead 头结点是Chead 以带a头节点的作为主链
{
b = head;
while (cur)
{
if (cur->data<0)
{
LNode tem=cur;
cur = cur->next;
s->next = cur;
//后插法B
p->next = tem;
tem->next = NULL;
p = p->next;
}
else if (cur->data > 0)
{
s->next = cur;
cur=cur->next;
s = s->next;
}
}
}
else if (count > 0)//说明第一个节点是Bhead 头结点是Chead 以带a头节点的作为主链
{
c = head;
while (cur)
{
if (cur->data > 0)
{
LNode tem = cur;
cur = cur->next;
s->next = cur;
//后插法B
p->next = tem;
tem->next = NULL;
p = p->next;
}
else if (cur->data < 0)
{
s->next = cur;
cur = cur->next;
s = s->next;
}
}
}
}
int main()
{
LinkList a;
int count = 0;
a.next = NULL;
LNode b =nullptr , c=nullptr ;
LNode p = &a;int data;
printf("请输入要储存的数据,以0为结束\n");
scanf("%d", &data);
while (data != 0)
{
LNode node = new LinkList;
node->next = a.next;
a.next = node;
node->data = data;
scanf("%d", &data);
}
printf("要储存的数据已储存在A表中!\n");
p = p->next;
if (p->data > 0)
{
count = 1;
c = p;
b = &a;
}
else if (p->data < 0)
{
count = -1;
b = p;
c = &a;
}
//第一个节点作为B或C表的头结点 A的头结点作为另外一个的头结点
else if (p->data == 0)
{
printf("A表中暂无储存数据!\n");
return 0;
}
SplitLink(a, b, c,count);
if (count < 0)
{
printf("所有数据已处理完毕,其中 B 表中数据皆小于 0,B表数据为:");
printf("%d ", b->data);
OutputLink(*b);
printf("所有数据已处理完毕,其中 C 表中数据皆大于 0,C表数据为:");
OutputLink(*c);
}
if (count > 0)
{
printf("所有数据已处理完毕,其中 B 表中数据皆小于 0,B表数据为:");
OutputLink(*b);
printf("所有数据已处理完毕,其中 C 表中数据皆大于 0,C表数据为:");
printf("%d ", c->data);
OutputLink(*c);
}
return 0;
}
实验四:反转链表且不用新的储存节点空间
一 算法设计思路
用了四个函数:OutputLinkList、InputLinkList、ReverseLink、main
OutputLinkList:接受一个参数a,表示要输出的链表的头节点。函数内部首先定义了一个指针变量p,并将其指向链表的头结点a。然后通过循环遍历链表,依次输出每个节点的数据。在每次循环中,将p指向下一个节点,直到到达链表的末尾(即p为空)。最后,使用puts("")输出一个空行,以增加输出的可读性。
InputLinkList头插法建立链表,通过传入的头节点,将新插入的节点指针域指向头节点原指针域所指向位置,再建立新节点插入到头节点之后,最后将数据域赋值后输入数据进行下一次循环的判断,最后调用OutputLinkList将链表数值输出
ReverseLink: 反转链表A。首先,定义一个头节点指针p指向链表的头节点。然后,创建两个临时指针pre和head,并将它们分别指向第二个节点和第三个节点。接着,将头节点的next指针设置为NULL,表示链表已经处理完毕。
接下来,进入一个循环,循环会一直执行,直到没有更多的节点需要处理(即end为空)。在每次循环中,首先保存当前头节点的下一个节点到临时变量tem中,然后将前一个节点的next指针指向当前的头节点的下一个节点,实现将当前头节点的前插到链表头部的操作。
接着,将头节点的next指针指向原来的头节点,将原来的头节点设置为新的尾节点,并将当前头节点更新为前一个节点的下一个节点。如果新的头节点不为空,那么它就将end设置为新头节点的下一个节点。
当所有的节点都被处理后,会将最后一个节点的next指针指向原来的头节点,这样就完成了链表的反转。最后,它调用OutputLinkList(a)函数来输出反转后的链表。
Main:首先建立头节点a然后用InputLinkList函数头插法建立链表A,通过ReverseLink函数来对A链表进行反转
二 源程序代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <iostream>
using namespace std;
typedef struct LinkList {
int data;
struct LinkList* next;
}*LNode;
void OutputLinkList(LinkList a)
{
LNode p = &a;
p = p->next;
printf("目前表中数据为:");
while (p)
{
printf("%d ", p->data);
p = p->next;
}
puts("");
}
int InputLinkList(LinkList &a)
{
int data;
LNode p = &a;
printf("请输入要储存数据的个数");
int n = 0;
scanf("%d", &n);
for(int i=0;i<n;i++)
{
printf("请输入第%d个元素\n",i + 1);
scanf("%d", &data);
LNode node = new LinkList;
node->next = NULL;
node->data = data;
p->next = node;
p = p->next;
}
printf("数据已全部储存");
OutputLinkList(a);
return n;
}
void ReverseLink(LinkList& a,int n)
{
LNode p = &a;
LNode pre = p->next;
LNode head, end;
head = pre->next;
end = head->next;
p->next = NULL;
LNode cur=NULL;
while (end)
{
LNode tem = head;
pre->next = head->next;
head->next = p->next;//前插法
p->next = head;
cur = head;
head = pre->next;
if(head!=NULL)
end = head->next;
}
pre->next = head->next;
head->next = p->next;
p->next = head;
while (p->next != NULL)
p = p->next;
p->next = pre;
OutputLinkList(a);
}
int main()
{
LinkList a;
int n=InputLinkList(a);
ReverseLink(a, n);
return 0;
}
四 调试情况、设计技巧及体会
实验中出现的典型错误和修改方法:
1.在归并排序中,如果两个链表的头元素相等,可能会导致错误的排序结果。为了解决这个问题,可以在比较两个元素时,同时考虑它们的值和它们在原链表中的位置。
2.在插入节点时,如果插入位置不正确,可能会导致链表出现循环或者丢失节点。为了避免这种情况,可以在插入节点之前检查插入位置是否合法。
3.在删除节点时,如果删除的节点不存在,可能会导致链表出现错误。为了解决这个问题,可以在删除节点之前检查节点是否存在。
4.在对某个指针p进行赋值的时候,可能会因为p==NULL出现报错,解决方法:在对指针进行赋值操作时先对其所指节点的指针域进行判断。
5. 忘记检查插入位置是否合法。修改方法:在插入节点之前,先检查插入位置是否合法。如果插入位置小于头节点或大于尾节点,则不进行插入操作。
6. 忘记更新指针。修改方法:在插入、删除和查找节点时,要确保正确地更新相关节点的指针。例如,在插入节点时,要更新当前节点的前一个节点的指针;在删除节点时,要更新被删除节点的前一个节点和后一个节点的指针;在查找节点时,要更新指向目标节点的指针。
7. 未考虑边界条件。修改方法:在编写代码时,要考虑到边界条件的情况。例如,当链表为空或只有一个元素时,需要特别处理以避免出现错误。可以通过添加条件语句来检查链表的长度,并根据不同情况执行相应的操作。
8.未正确处理特殊情况。修改方法:在设计算法时,要考虑到各种可能的特殊情况,并对其进行适当的处理。例如,当链表中存在循环时,需要特殊处理以避免进入死循环;当链表中存在重复元素时,需要进行特殊处理以保持排序的正确性。
9. 未进行充分的测试和调试。修改方法:在解决问题时,要进行充分的测试和调试以确保算法的正确性和效率。通过编写测试用例并观察输出结果,可以验证算法的正确性。同时,如果遇到错误或异常情况,要进行调试以找出问题所在并进行修复。
实验中学到的技巧和积累的经验:
在实验四对链表进行原地逆置的过程中,可以使用三个指针,分别指向第一个第二个第三个数据节点,然后将第一个指针指向的节点和第三个指针指向的节点交换,再将三个指针分别向后移动,这样不需要使用递归就可以通过循环将链表进行逆置。在实验二中,对规定位置的元素进行查找和删除时要检查位置的规范性合理性,对于特殊的情况要特殊处理。在插入节点时,可以使用尾插法。尾插法不需要遍历整个链表来找到插入位置,而是直接将新节点添加到链表的尾部。这样可以节省时间复杂度。在删除节点时,可以使用双指针法。通过使用两个指针,一个指向当前节点的前一个节点,另一个指向当前节点,可以方便地实现节点的删除操作在查找节点时,可以使用迭代或递归方法。迭代方法通过遍历链表来查找目标节点,而递归方法可以通过比较当前节点的值与目标值来缩小搜索范围。在归并排序中,可以使用自底向上的归并方法。自底向上的归并方法首先将链表分割成单个元素,然后逐步合并这些元素以构建有序链表。这种方法可以减少空间复杂度。在逆转链表时,可以使用迭代或递归方法。迭代方法通过修改指针的方向来实现链表的逆转,而递归方法可以通过反转子链表来实现链表的逆转。在设计算法时,要注意边界条件和特殊情况的处理。例如,当链表为空或只有一个元素时,需要特别处理以避免出现错误。在编写代码时,要遵循良好的编程实践。这包括使用有意义的变量名和函数名、添加适当的注释、保持代码简洁和可读性等。