作业题目:线性表的基本存储结构的实现与应用
顺序表与单链表是线性表的两种最基本的存储结构,而静态链表是两者的 完美结合,是系统进行动态存储分配的方法基础。线性表的这三种存储结构不但是其他数据结构(如树形结构、图型结构、集合等)存储方法的重要基础,同时本身也有广泛的应用。
作业要求
1. 实现线性表的顺序存储结构(SeqList)和链式存储结构(LinkList)。
2. 在上述存储结构的基础上,分别实现以下算法:
① 删除给定元素的算法。
② 对于已排好序的线性表,删除所有重复元素的算法。
③ 线性表“逆置”算法。
④ 线性表循环左移/右移 k 位的算法。
⑤ 合并两个已排好序的线性表的算法。
3. 选做:(可以不做,供学有余力、有兴趣的同学探索)
① 你能实现线性表的静态链表存储结构吗?
② 你能在静态链表上实现线性表的“逆置”算法吗?
存储结构
struct Link
{
int data;//数据域
struct Link *next;//指针域
};
typedef struct Link Node;
部分基本操作
新建一个结点
Node *Append(Node *head)
{
Node *p=NULL,*pr=head;
int data;
p=(Node*)malloc(sizeof(Node));
if(p==NULL)
{
printf("No enough memory to allocate!");
exit(0);
}
if(head==NULL)//原表为空
{
head=p;
}
else
{
while(pr->next!=NULL)
{
pr=pr->next;
}
pr->next=p;
}
printf("Input node data:");
scanf(" %d",&data);
p->data=data;
p->next=NULL;
return head;
}
显示表中结点
void Display(Node *head)
{
Node *p=head;
int j=1;
while(p!=NULL)
{
printf("%5d%10d\n",j,p->data);
p=p->next;
j++;
}
}
释放链表所占内存
void DeleteMemory(Node *head)
{
Node *p=head,*pr=NULL;
while(p!=NULL)
{
pr=p;
p=p->next;
free(pr);
}
}
升序排序的链表中插入一个结点
Node *InsertNode(Node *head,int Nodedata)
{
Node *pr=head,*p=head,*temp=NULL;
p=(Node *)malloc(sizeof(Node));//p指向待插结点
if(p==NULL)//申请失败
{
printf("No enough memory!");
exit(0);
}
p->next=NULL;//为p指向结点赋值为空
p->data=Nodedata;
if(head==NULL)//原链表为空表
{
head=p;//head指向新建结点p
}
else
{
while(pr->data<Nodedata&&pr->next!=NULL)
{
temp=pr;//temp保存当前结点指针
pr=pr->next;//pr指向当前结点的下一结点
}
if(pr->data>=Nodedata)
{
if(pr==head)//若在头结点处插入新结点
{
p->next=head;//新结点指向原链表的头结点
head=p;//head指向新结点
}
else//链表中插入新结点
{
pr=temp;
p->next=pr->next;//新结点指向下一结点
pr->next=p;//前一结点指向新结点
}
}
else
{
pr->next=p;//末结点指向新结点
}
}
return head;
}
作业中算法
1.删除给定元素
先对空表情况进行单独说明,然后遍历该线性表,查找到给定元素后修改前后指针,将其所在结点释放,未找到进行说明。
Node *DeleteNode(Node *head,int Nodedata)
{
Node *p=head,*pr=head;
if(head==NULL)//表为空
{
printf("Table is empty!");
return head;
}
while(Nodedata!=p->data&&p->next!=NULL)
{
pr=p;
p=p->next;
}
if(Nodedata==p->data)
{
if(p==head)
{
head=p->next;
}
else
{
pr->next=p->next;
}
free(p);
}
else
{
printf("This node has not been found!");
}
return head;
}
2.删除重复元素(升序表)
Node *Delete_Element_duplicate(Node *head)
{
Node *p=head,*pr=head,*temp=NULL;
if (head == NULL)
{
printf("Table is empty!");
return head;
}
p=pr->next;
while(p != NULL)
{
if(p->data == pr->data)
{
pr->next=p->next;
temp=p;
p=p->next;
free(temp);
}
else
{
pr=pr->next;
p=p->next;
}
}
return head;
}
3.将线性表逆置
当表中没有元素或者只有一个元素的时候,无需进行操作,单独说明。其他情况,用p指向头结点,将头结点指向空,将p依次后移,temp记录p移动前指向的结点,head指向temp前一结点,使temp->next=head,完成该结点的一个逆置,循环直到最后一个结点,对最后一个结点的next域和head单独修改。
Node *Reverse(Node *head)
{
Node *p=head,*temp=NULL;
if(head == NULL)
{
printf("Table is empty!");
return head;
}
else if(head->next == NULL)
{
printf("success!");
return head;
}
else
{
head=NULL;
while(p->next != NULL)
{
temp=p;
p=p->next;
temp->next=head;
head=temp;
}
head=p;
p->next=temp;
}
return head;
}
4.循环左移
先将原来的线性表头尾链接形成一个环形链表,再在适当的位置将其断开,这里展示了循环左移的算法,右移同理。
Node *Cyclic_Move(Node *head,int k)
{
int i=0;
Node *p=head,*pr=head;
while(p->next != NULL)
{
p=p->next;
}
p->next=head;
for(i=0;i<k;i++)
{
pr=head;
head=head->next;
}
pr->next=NULL;
return head;
}
5.合并两个有序表
申请一片新的空间用于存放合并后的线性表,也可以在其中一个线性表上进行将另一个表的元素添加上来的操作,但这样会破坏原有表的结构,题目未作说明两种思路均可。
Node *Merge(Node *head1,Node *head2,Node *head)
{
Node *p=head1,*q=head2;//head为合并后的链表
Node *s=NULL;
Node *pr=NULL;
s=(Node *)malloc(sizeof(Node));//存放新建表的头结点
head=s;
pr=head;
if(p->data>=q->data)
{
pr->data=q->data;
q=q->next;
}
else
{
pr->data=p->data;
p=p->next;
}
//头结点建立完毕,此时pr指向头节点
while(p != NULL&&q != NULL)
{
s=(Node *)malloc(sizeof(Node));
if(s==NULL)
{
printf("No enough memory!");
exit(0);
}
pr->next=s;//建立s成功,pr指向新结点
if(p->data>=q->data)
{
s->data=q->data;
q=q->next;
}
else
{
s->data=p->data;
p=p->next;
}
pr=s;//pr指向新建完毕的结点
}
if(p == NULL)//p所指向的链表已空
{
while(q != NULL)
{
s=(Node *)malloc(sizeof(Node));
pr->next=s;
s->data=q->data;
q=q->next;
pr=s;
}
}
else//(q->next == NULL)
{
while(p != NULL)
{
s=(Node *)malloc(sizeof(Node));
pr->next=s;
s->data=p->data;
p=p->next;
pr=s;
}
}
pr->next=NULL;
return head;
}
测试结果
Code::Blocks下测试结果如下:
合并算法的测试如下:
反思与总结
- 命名一定要英文命名,包括上一级文件夹的名字(整个文件路径),否则debug时可能出现故障!(在用CodeBlocks进行调试时,提示这样的错误信息 Debugger finished with status 0 此时主要错误原因为文件路径以及文件名中含中文字符。)
- 没有编写menu函数(太懒了),直接在main中测试代码。
- 对合并算法未考虑其中一表为空的情况,且没有对这种情况进行测试。
- 主函数中对线性表的输入代码过于庞大,可以写一个函数来简化数据输入过程。
- 合并算法开头处对head的处理与对pre处理很类似,考虑是否可以对循环体进行改进,减少代码量。
- 有时候检查函数算法错误时,错误也可能在测试函数的语句中,不要忽略(设置断点时注意)。
- 注意函数的返回值类型,不为空时一定要赋值给相应的变量(xiaolaji在编写时就将返回值为指针类型的函数当作空函数来使用,编译器没有报错,一直以为错误在函数内部,找了好久都没发现错误,浪费了好多时间)。
- 部分基本操作代码学习参考C语言程序设计(苏小红主编第四版)第12章,和自己相比代码更加简洁,思路更加清晰,尤其是对特殊情况的处理考虑十分周到,需要学习。
完整代码
#include <stdio.h>
#include <stdlib.h>
struct Link
{
int data;//数据域
struct Link *next;//指针域
};
typedef struct Link Node;
/*新建一个结点并添加到链表末尾,返回添加后链表的头指针*/
Node *Append(Node *head)
{
Node *p=NULL,*pr=head;
int data;
p=(Node*)malloc(sizeof(Node));
if(p==NULL)
{
printf("No enough memory to allocate!");
exit(0);
}
if(head==NULL)//原表为空
{
head=p;
}
else
{
while(pr->next!=NULL)
{
pr=pr->next;
}
pr->next=p;
}
printf("Input node data:");
scanf(" %d",&data);
p->data=data;
p->next=NULL;
return head;
}
/*依次显示表中结点*/
void Display(Node *head)
{
Node *p=head;
int j=1;
while(p!=NULL)
{
printf("%5d%10d\n",j,p->data);
p=p->next;
j++;
}
}
/*释放head所指向链表所占用的内存*/
void DeleteMemory(Node *head)
{
Node *p=head,*pr=NULL;
while(p!=NULL)
{
pr=p;
p=p->next;
free(pr);
}
}
/*删除链表中给定元素的结点,返回head*/
Node *DeleteNode(Node *head,int Nodedata)
{
Node *p=head,*pr=head;
if(head==NULL)//表为空
{
printf("Table is empty!");
return head;
}
while(Nodedata!=p->data&&p->next!=NULL)
{
pr=p;
p=p->next;
}
if(Nodedata==p->data)
{
if(p==head)
{
head=p->next;
}
else
{
pr->next=p->next;
}
free(p);
}
else
{
printf("This node has not been found!");
}
return head;
}
/*在已按升序排序的链表中插入一个结点,返回插入后的head*/
Node *InsertNode(Node *head,int Nodedata)
{
Node *pr=head,*p=head,*temp=NULL;
p=(Node *)malloc(sizeof(Node));//p指向待插结点
if(p==NULL)//申请失败
{
printf("No enough memory!");
exit(0);
}
p->next=NULL;//为p指向结点赋值为空
p->data=Nodedata;
if(head==NULL)//原链表为空表
{
head=p;//head指向新建结点p
}
else
{
while(pr->data<Nodedata&&pr->next!=NULL)
{
temp=pr;//temp保存当前结点指针
pr=pr->next;//pr指向当前结点的下一结点
}
if(pr->data>=Nodedata)
{
if(pr==head)//若在头结点处插入新结点
{
p->next=head;//新结点指向原链表的头结点
head=p;//head指向新结点
}
else//链表中插入新结点
{
pr=temp;
p->next=pr->next;//新结点指向下一结点
pr->next=p;//前一结点指向新结点
}
}
else
{
pr->next=p;//末结点指向新结点
}
}
return head;
}
/*对排好序的线性表,删除所有重复元素*/
Node *Delete_Element_duplicate(Node *head)
{
Node *p=head,*pr=head,*temp=NULL;
if (head == NULL)
{
printf("Table is empty!");
return head;
}
p=pr->next;
while(p != NULL)
{
if(p->data == pr->data)
{
pr->next=p->next;
temp=p;
p=p->next;
free(temp);
}
else
{
pr=pr->next;
p=p->next;
}
}
return head;
}
/*逆置线性表*/
Node *Reverse(Node *head)
{
Node *p=head,*temp=NULL;
if(head == NULL)
{
printf("Table is empty!");
return head;
}
else if(head->next == NULL)
{
printf("success!");
return head;
}
else
{
head=NULL;
while(p->next != NULL)
{
temp=p;
p=p->next;
temp->next=head;
head=temp;
}
head=p;
p->next=temp;
}
return head;
}
/*循环向左移动k位*/
Node *Cyclic_Move(Node *head,int k)
{
int i=0;
Node *p=head,*pr=head;
while(p->next != NULL)
{
p=p->next;
}
p->next=head;
for(i=0;i<k;i++)
{
pr=head;
head=head->next;
}
pr->next=NULL;//where are the bugs?
return head;
}
/*合并两个排好序的线性表(升序)*/
Node *Merge(Node *head1,Node *head2,Node *head)
{
Node *p=head1,*q=head2;//head为合并后的链表
Node *s=NULL;
Node *pr=NULL;
s=(Node *)malloc(sizeof(Node));//存放新建表的头结点
head=s;
pr=head;
if(p->data>=q->data)
{
pr->data=q->data;
q=q->next;
}
else
{
pr->data=p->data;
p=p->next;
}
//头结点建立完毕,此时pr指向头节点
while(p != NULL&&q != NULL)
{
s=(Node *)malloc(sizeof(Node));
if(s==NULL)
{
printf("No enough memory!");
exit(0);
}
pr->next=s;//建立s成功,pr指向新结点
if(p->data>=q->data)
{
s->data=q->data;
q=q->next;
}
else
{
s->data=p->data;
p=p->next;
}
pr=s;//pr指向新建完毕的结点
}
if(p == NULL)//p所指向的链表已空
{
while(q != NULL)
{
s=(Node *)malloc(sizeof(Node));
pr->next=s;
s->data=q->data;
q=q->next;
pr=s;
}
}
else//(q->next == NULL)
{
while(p != NULL)
{
s=(Node *)malloc(sizeof(Node));
pr->next=s;
s->data=p->data;
p=p->next;
pr=s;
}
}
pr->next=NULL;
return head;
}
int main()
{
Node *head=NULL;//头指针
char c;
printf("Do you want to append a new node(Y/N)");
scanf(" %c",&c);
while(c=='Y'||c=='y')
{
head=Append(head);
printf("Do you want to append a new node?(Y/N)");
scanf(" %c",&c);
}
Display(head);//显示当前链表中各节点信息
printf("table has been established!\n");
printf("The result of deleting number 3\n:");
head=DeleteNode(head,3);
Display(head);
printf("The result of deleting duplicate elements:\n");
head=Delete_Element_duplicate(head);
Display(head);
printf("The result of reversing the table:\n");
head=Reverse(head);
Display(head);
printf("The result of moving 3 units to the left\n");
head=Cyclic_Move(head,3);
Display(head);
DeleteMemory(head);
/*测试合并算法用*//*
Node *head1=NULL;
Node *head2=NULL;
Node *head=NULL;
char c;
printf("Do you want to append a new node for table one ?(Y/N)");
scanf(" %c",&c);
while(c=='Y'||c=='y')
{
head1=Append(head1);
printf("Do you want to append a new node?(Y/N)");
scanf(" %c",&c);
}
printf("table one has been established!\n");
Display(head1);//显示当前链表中各节点信息
printf("Do you want to append a new node for table two ?(Y/N)");
scanf(" %c",&c);
while(c=='Y'||c=='y')
{
head2=Append(head2);
printf("Do you want to append a new node?(Y/N)");
scanf(" %c",&c);
}
printf("Table two has been established!\n");
Display(head2);//显示当前链表中各节点信息
printf("Table merged by table 1 and table 2 is:\n");
head=Merge(head1,head2,head);
Display(head);
DeleteMemory(head1);
DeleteMemory(head2);*/
return 0;
}