第1关:循环链表
任务描述
本关任务:根据所学循环链表的基础知识,完成循环链表删除数据功能的程序编写并通过所有测试用例。
相关知识
为了完成本关任务,你需要掌握:1.如何获取数组的长度,2.如何遍历数组。
循环链表简介
简单来说,单链表像一个小巷,无论怎么样最终都能从一端走到另一端,循环链表则像一个有传送门的小巷,因为循环链表当你以为你走到结尾的时候,其实你又回到了开头。循环链表和非循环链表其实创建的过程以及思路几乎完全一样,唯一不同的是,非循环链表的尾结点指向空(NULL),而循环链表的尾指针指向的是链表的开头。通过将单链表的尾结点指向头结点的链表称之为循环单链表(Circular linkedlist)。
图 1 循环链表
如图1所示,循环链表将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环。循环链表与单链表的主要差异在于循环的判断上,原来是判断p->next
是否为空,现在则是p->next
不等于头结点,则循环未结束。
循环链表的实现
1、创建链表
如同单链表的创建,我们需要先创建一个头结点并且给其开辟内存空间,但与单链表不同的是,我们需要在开辟内存空间成功之后将头结点的next
指向head
自身。当需要进行插入时,我们首先创建一个新的节点,将原有链表尾结点的next
指针修改指向到新的结点,新结点的next
指针再重新指向头部结点,然后逐步进行这样的插入操作,最终完成整个单项循环链表的创建。代码示例如下:
int insert_list(list *head){
int data; //插入的数据类型
printf("请输入要插入的元素:");
scanf("%d",&data);
list *node=initlist();
node->data=data; //初始化一个新的结点,准备进行链接
if(head!=NULL){
list *p=head;
//找到最后一个数据
while(p->next!=head){
p=p->next;
}
p->next=node;
node->next=head;
return 1;
}else{
printf("头结点已无元素\n");
return 0;
}
}
2、删除操作
如图2所示,循环单链表的删除操作可以参考单链表的删除操作,其都是找到需要删除的结点,将其前一个结点的next
指针直接指向删除结点的下一个结点即可,但需要注意的是尾节点和头结点的特判,尤其是尾结点,因为删除尾节点后,尾节点前一个结点就成了新的尾节点,这个新的尾节点需要指向的是头结点而不是空,其重点可以记录为当前的前一节点.next=自身结点.next
这样的操作可以省去头尾结点的特判。
图 2 循环链表的删除操作
示例代码:
int delete_list(list *head) {
if(head == NULL) {
printf("链表为空!\n");
return 0;
}
list *temp = head;
list *ptr = head->next;
int del;
printf("请输入你要删除的元素:");
scanf("%d",&del);
while(ptr != head) {
if(ptr->data == del) {
if(ptr->next == head) {
temp->next = head;
free(ptr);
return 1;
}
temp->next = ptr->next;//核心删除操作代码
free(ptr);
//printf("元素删除成功!\n");
return 1;
}
temp = temp->next;
ptr = ptr->next;
}
printf("没有找到要删除的元素\n");
return 0;
}
编程要求
在右侧编辑器中的 Begin-End 之间补充 C 语言代码,完成对循环链表删除功能的实现,其数据内容通过 scanf 从后台获取。
测试说明
平台将使用测试集运行你编写的程序代码,若全部的运行结果正确,则通关。
测试输入:
9 8 10 2 0 // 0是结束输入的信号
预期输出:
输入结点数据中...
--------循环链表初始元素------
9 8 10 2
--------删除第二个结点后------
9 10 2
开始你的任务吧,祝你成功!
截止后通关代码:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include<stdlib.h>
#define ERROR 0
#define OK 1
typedef int EleType;
typedef struct CLinkNode
{
EleType data;
struct CLinkNode *next;
}CLinkNode,*CLinkList;
/*
初始化循环链表
*/
int InitCLinkList(CLinkList *list)
{
if (list == NULL)
{
return ERROR;
}
int data = 0;
CLinkNode* target = NULL;
CLinkNode* head_node = NULL;
printf("输入结点数据中...\n");
while (1)
{
scanf("%d", &data);
if (data == 0)
{
//退出循环标志,用户输入0 表示结束输入数据
break;
}
if (*list == NULL)
{
CLinkNode* head= (CLinkNode*)malloc(sizeof(CLinkNode));
//分配结点空间失败
if (head == NULL)
{
exit(0);
}
*list = head;//链表指向头结点
CLinkNode* node = (CLinkNode*)malloc(sizeof(CLinkNode));
if (node == NULL)
{
exit(0);
}
node->data = data;
node->next = head;
head->next = node;
}
else
{
for (target = (*list)->next; target->next != *list; target = target->next);
head_node = target->next;
CLinkNode* node = (CLinkNode*)malloc(sizeof(CLinkNode));
if (node == NULL)
{
exit(0);
}
node->data = data;
node->next = head_node;
target->next = node;//将新结点插入尾部
}
}
return OK;
}
/*
往链表指定位置插入数据
list 循环链表
loc 第loc位置插入元素,loc 从1 开始计数
data 插入元素的数据域
*/
int InsertCLinkNode(CLinkList list,int loc, EleType data)
{
if (list == NULL || loc < 1)
return ERROR;
/*
循环目的:找到第loc-1位置结点
*/
int i = 1;
CLinkNode* node = list;//刚开始node指向头结点
while (node->next!=list && i < loc)
{
node = node->next;
i++;
}
if (i == loc)
{
CLinkNode* new_node = (CLinkNode*)malloc(sizeof(CLinkNode));
if (new_node == NULL)
{
exit(0);
}
new_node->data = data;
new_node->next = node->next;//新结点指针域 指向前驱结点的后继结点
node->next = new_node;//将新结点加入链表
}
else
{
return ERROR;
}
return OK;
}
/*
删除指定结点,通过指针返回删除结点的数据,并保存至data
*/
int DelCLinkNode(CLinkList list,int loc, EleType* data)
{
if (list == NULL || loc < 1)
return ERROR;
/*
循环目的:找到第loc-1位置结点
*/
int i = 1;// 按人类的读法 i表示第i个位置 和 loc 表达意思一致
CLinkNode* node = list;//刚开始node指向头结点
while (node->next != list && i < loc)
{
node = node->next;
i++;
}
//循环结束 node 指向 loc-1 位置 且 node 不能为尾结点,为什么不能为尾结点?因为不能删除 位置上没有元素的结点!
if (i == loc && node->next != list)
{
// 请在下面的Begin-End之间补充代码,完成对结点的删除。
/********** Begin *********/
CLinkNode *r;
r=node->next;
node->next=r->next;
free(r);
/********** End **********/
}
return OK;
}
/*
展示循环链表元素
*/
int ShowCLinkList(CLinkList list)
{
if (list == NULL)
{
return ERROR;
}
CLinkNode* target = NULL;
for (target = list->next; target != list; target = target->next)
printf("%d \t",target->data);
printf("\n");
return OK;
}
int main(int argc, char *argv[])
{
int flag = 0;
CLinkList list = NULL;
list = NULL;
InitCLinkList(&list);
printf("--------循环链表初始元素------\n");
ShowCLinkList(list);
int loc = 2;
int data = 0;
DelCLinkNode(list, loc, &data);
printf("--------删除第二个结点后------\n");
ShowCLinkList(list);
return 0;
}
第2关:双向链表
任务描述
本关任务:根据所学双向链表的基础知识,完成双向链表插入结点功能的程序编写并通过所有测试用例。
相关知识
为了完成本关任务,你需要掌握:
1、双向链表的基本概念;
2、双向链表的实现。
双向链表简介
双向链表可以简称为双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。使用这种数据结构,我们可以不再拘束于单链表的单向创建于遍历等操作,大大减少了在使用中存在的问题。
图 1 双链表结点
在单链表中,我们有一个数据域,还有一个指针域,数据域用来存储相关数据,而指针域则负责链表之间的“联系”。而在双向链表中,如图1所示,我们需要有两个指针域,一个负责向后连接,一个负责向前连接。一个完整的双向链表中应该是头结点的pre
指针指为空,尾结点的next
指针指向空,其余结点前后相链。双向链表中的结点可用代码表示为:
typedef struct List{
int data; // 数据域
struct List *next; // 向后的指针
struct List *front; // 向前的指针
};
双链表的实现
1、创建双链表
对于创建双向链表,我们需要先创建头结点再逐步的进行添加,请注意,双向链表的头结点是有数据元素的,也就是头结点的data
域中是存有数据的,这与一般的单链表是不同的。对于逐步添加数据,我们采取的做法是,开辟一段新的内存空间作为新的结点,为这个结点的data
进行赋值,然后将已有链表的上一个结点的next
指针指向自身,自身的pre
指针指向上一个结点。参考代码如下:
//创建双链表
line* initLine(line * head){
int number,pos=1,input_data;
//三个变量分别代表结点数量,当前位置,输入的数据
printf("请输入创建结点的大小\n");
scanf("%d",&number);
if(number<1){
return NULL;
} //输入非法直接结束
head=(line*)malloc(sizeof(line)); //头结点创建
head->pre=NULL;
head->next=NULL;
printf("输入第%d个数据\n",pos++);
scanf("%d",&input_data);
head->data=input_data;
line * list=head;
while (pos<=number) {
line * body=(line*)malloc(sizeof(line));
body->pre=NULL;
body->next=NULL;
printf("输入第%d个数据\n",pos++);
scanf("%d",&input_data);
body->data=input_data;
list->next=body;
body->pre=list;
list=list->next;
}
return head;
}
2、插入操作
对于每一次双向链表的插入操作,我们首先需要创建一个独立的结点并通过malloc
操作开辟相应的空间,其次我们选中这个新创建的独立节点,将其的pre
指针指向所需插入位置的前一个结点,同时,其所需插入的前一个结点的next
指针修改指向为该新的结点,同理,该新的结点的next
指针将会指向一个原本的下一个结点,而修改下一个结点的pre
指针为指向新结点自身,这样的一个操作我们称之为双向链表的插入操作。
图 2 双链表的插入
3、删除操作
如图3所示,双链表的删除操作过程为:选择需要删除的结点,选中这个结点的前一个结点,将前一个结点的next
指针指向自己的下一个结点,同时,选中该节点的下一个结点,将下一个结点的pre
指针修改指向为自己的上一个结点,这样产生的效果就是在进行遍历的时候直接将这一个结点给跳过了。在这样的指针修改操作之后,我们释放删除结点,归还空间给内存,这样的操作我们称之为双链表的删除操作。
图 3 双链表的删除
4、遍历
如同单链表的遍历一样,利用next
指针逐步向后进行索引即可,注意判断这里,我们既可以用while(list)
的操作直接判断是否链表为空,也可以使用while(list->next)
的操作判断该链表是否为空,其下一节点为空和本结点是否为空的判断条件是一样的效果,当然了,善用双向链表的pre
指针进行有效的遍历也是值得去尝试的。
编程要求
在右侧编辑器中的 Begin-End 之间补充 C 语言代码,完成对双向链表插入结点功能的实现,其数据内容通过 scanf 从后台获取。
测试说明
平台将使用测试集运行你编写的程序代码,若全部的运行结果正确,则通关。
测试输入:
31 32 1 7 3 0
预期输出:
输入数据中...
链表结构为:31 <-> 32 <-> 1 <-> 7 <-> 3 <-> 0
链表中第 4 个节点的直接前驱是:1
开始你的任务吧,祝你成功!
截止后通关代码:
#include <stdio.h>
#include <stdlib.h>
//节点结构
typedef struct line {
struct line * prior;
int data;
struct line * next;
}line;
//双链表的创建函数
line* initLine(line * head);
//输出双链表的函数
void display(line * head);
int main() {
//创建一个头指针
line * head = NULL;
//调用链表创建函数
head = initLine(head);
printf("链表结构为:");
//输出创建好的链表
display(head);
//显示双链表的优点
printf("链表中第 4 个节点的直接前驱是:%d", head->next->next->next->prior->data);
return 0;
}
line* initLine(line * head) {
printf("输入数据中...\n");
int data[6]={0};
for(int i=0;i<6;i++){
scanf("%d",&data[i]);
}
line * list = NULL;
//创建一个首元节点,链表的头指针为head
head = (line*)malloc(sizeof(line));
//对节点进行初始化
head->prior = NULL;
head->next = NULL;
head->data = data[0];
//声明一个指向首元节点的指针,方便后期向链表中添加新创建的节点
list = head;
for (int i = 1; i <= 5; i++) {
// 请在下面的Begin-End之间补充代码,插入结点,其中结点数据为data[i]。
/********** Begin *********/
line *s =(line*)malloc(sizeof(line));
s->prior = NULL;
s->next = NULL;
s->data=data[i];
if(list==NULL){
list=s;
}
else{
list->next=s;
s->prior=list;
list=s;
}
/********** End **********/
}
//返回新创建的链表
return head;
}
void display(line * head) {
line * temp = head;
while (temp) {
//如果该节点无后继节点,说明此节点是链表的最后一个节点
if (temp->next == NULL) {
printf("%d\n", temp->data);
}
else {
printf("%d <-> ", temp->data);
}
temp = temp->next;
}
}
第3关:线性表综合应用
任务描述
本关任务:根据所学链表的基础知识,完成基于链表进行一元多项式相加的程序编写并通过所有测试用例。
相关知识
为了完成本关任务,你需要掌握:
1、如何表示一元多项式;
2、一元多项式相加。
一元多项式相加
1、任务描述
求两个一元多项式 A(x)=a0+a1x+a2x2+…+anxn 和 B(x)=b0+b1x+b2x2+…+bmxm 的和。一元多项式相加的运算规则非常简单,两个多项式中指数相同的项对应系数相加,若相加的和不为零,则构成相加结果多项式中的一项,所有指数不相同的项均复制到相加结果多项式中。
2、实现思路
通过链表实现,会更为简单直观。用链表中的每个结点表示多项式中的每一项,多项式每一项都是由数据域(包含系数和指数)和指针域构成的,所以在定义表示结点的结构体时,可如下所示进行定义:
typedef struct PLnode{
//数据域,coef 表示系数,expn 表示指数
float coef;
int expn;
//指针域
struct PLnode *next;
}PLnode,*PLinkList;
如图1所示,为一元多项式 A(x)=7+3x+9x8+5x17 和 B(x)=8x+22x7−9x8 的链表示意图。假设指针qa
和qb
分别指向多项式 A
和多项式 B
中当前进行比较的某个结点,则比较两个结点的指数项,有以下3种情况:
-
指针
qa
所指结点的指数值小于指针qb
所指结点的指数值,则应摘除qa
所指结点插入到“和多项式”链表中去; -
指针
qa
所指结点的指数值大于指针qb
所指结点的指数值,则应摘除qb
所指结点插入到“和多项式”链表中去; -
指针
qa
所指结点的指数值等于指针qb
所指结点的指数值,则将两个结点的系数相加:若和不为0,则修改qa
所指结点的系数值,同时释放qb
所指结点;若和为0,则应从多项式A
的链表中删除相应结点,并释放指针qa
和qb
所指结点。
图 1 一元多项式
编程要求
在右侧编辑器中的 Begin-End 之间补充 C 语言代码,完成基于链表的一元多项式相加功能的实现,其数据内容通过 scanf 从后台获取。
测试说明
平台将使用测试集运行你编写的程序代码,若全部的运行结果正确,则通关。
测试输入:
4
1 9
2 4
9 1
7 4
1
9 2
预期输出:
La=x^9+2x^4+9x+7x^4
Lb=9x^2
计算结果为:
Lc=9x^2+x^9+2x^4+9x+7x^4
开始你的任务吧,祝你成功!
截止后通关代码:
#include <stdio.h>
#include <stdlib.h>
typedef struct PLnode{
//数据域,coef 表示系数,expn 表示指数
float coef;
int expn;
//指针域
struct PLnode *next;
}PLnode,*PLinkList;
//一元多项式的链表表示创建函数,输入 m 项的系数和指数,建立表示一元多项式的有序链表L
void creatpolyn(PLinkList L, int m){
int i;
float coef;
int expn;
PLinkList tail,n;
L->coef = m;
L->expn = -1;
tail = L;
for(i=1 ; i<=m ; i++){
n = (PLinkList)malloc(sizeof(PLnode));
scanf("%f",&coef);
scanf("%d",&expn);
n->coef = coef;
n->expn = expn;
n->next = NULL;
tail->next = n;
tail = n;
}
}
//完成多项式相加运算,即 Lc = La + Lb,并销毁一元多项式 Lb
PLinkList addpolyn(PLinkList La , PLinkList Lb){
int x,len;
float y;
PLinkList Lc,pa,pb,pc,u;
Lc = La;
len = 0;
pc = Lc;
//另pa,pb 指向La 、Lb 的首元结点
pa = La->next;
pb = Lb->next;
//通过 pa,pb 遍历链表 La、Lb,只有两指针同时存在时,才需要讨论
while(pa && pb){
x = pa->expn-pb->expn;
//判断pa 所指结点的指数与pb 所指结点指数的大小关系
if(x<0){
//如果小,则找去 qa 结点到Lc 上
pc = pa;
len++;
pa = pa->next;
}
//如果相等,则判断两结点的系数和是否为0
else if(x == 0){
// 请在下面的Begin-End之间补充代码,完成一元多项式的相加。
/********** Begin *********/
y=pa-> coef +pb ->coef;
if(y != 0)
{pa -> coef=y;
pc = pa;
len++;
}else{
pc -> next=pa-> next;
free(pa);
}
pa =pc -> next;
u= pb;
pb=pb->next;
free(u);
/********** End **********/
}
//如果pb 所指结点指数值小,则摘取pb所指结点到 LC链表上
else{
u = pb->next;
pb->next= pa;
pc->next=pb;
pc = pb;
len++;
pb = u;
}
}
//由于是在 La 上进行一元多项式的加和,所以如果运行过程 pa 不再有结点,而pb 上有,则需要将pb剩余结点链接到 Lc 上
if(pb){
pc->next = pb;
}
//计算 Lc 的长度
while(pc){
pc = pc->next;
if(pc){
len++;
}
}
//Lc 的头结点中记录Lc 链表的长度
Lc->coef = len;
//加和完成的同时,释放Lb 结点
free(Lb);
return Lc;
}
//根据链表存储信息。输出结点 q
void printpoly(PLinkList q){
if(q->expn == 0){
printf("%.0f",q->coef);
}
else if(q->expn == 1){
if(q->coef == 1){
printf("x");
}
else if (q->coef == -1){
printf("-x");
}
else{
printf("%.0f",q->coef);
printf("x");
}
}
else if (q->coef == 1){
printf("x^%d",q->expn);
}
else if(q->coef == -1){
printf("-x^%d",q->expn);
}
else{
printf("%.0fx^%d",q->coef,q->expn);
}
}
//输出一元多项式L
void printpolyn(PLinkList L){
int n;
PLinkList p;
p = L->next;
n = 0;
while(p){
n++;
if(n == 1){
printpoly(p);
}else if(p->coef>0){
printf("+");
printpoly(p);
}else{
printpoly(p);
}
p = p->next;
}
}
int main(){
PLinkList La,Lb,Lc;
int m,n;
//根据 n 的值,创建链表La
scanf("%d",&n);
La = (PLinkList)malloc(sizeof(PLnode));
creatpolyn(La,n);
//根据 m 的值,创建 Lb
scanf("%d",&m);
Lb = (PLinkList)malloc(sizeof(PLnode));
creatpolyn(Lb,m);
//输出La和Lb
printf("La=");
printpolyn(La);
printf("\nLb=");
printpolyn(Lb);
//计算La+Lb,结果保存在 Lc中
printf("\n计算结果为:");
Lc = addpolyn(La,Lb);
printf("\nLc=");
printpolyn(Lc);
return 0;
}