408数据结构2019年第41题代码实战
【题目】设线性表L=(a1, a2, a3, … , an-2, an-1, an)采用带头结点的单链表保存,链表中的结点定义如下:
typedef struct node{
int data;
struct node* next;
}NODE;
请设计一个**空间复杂度为 O(1)**且时间上尽可能高效的算法,重新排列 L 中的各结点,得到线性表 L’=(a1 ,an , a2, an-1 ,a3 , an-2,…)。要求:
(1) 给出算法的基本设计思想。
(2)根据设计思想,采用 C或 C++语言描述算法,关键之处给出注释。
(3) 说明你所设计的算法的时间复杂度。
解答:
(1)设计思想见代码中的注释
(2)代码实战
#include <stdio.h>
#include<stdlib.h>
typedef int ElemType;
typedef struct LNode{
ElemType data;//数据域
struct LNode *next;//指针域
}LNode,*LinkList;
//LNode*是结构体指针,和LinkList完全等价的
//尾插法新建链表
void list_tail_insert(LNode* &L)
{
L= (LinkList)malloc(sizeof(LNode));//申请头节点空间,头指针指向头节点
L->next=NULL;
ElemType x;
scanf("%d",&x);
LNode *s,*r=L;//s是用来指向申请的新结点,r始终指向链表尾部
while(x!=9999)
{
s=(LinkList)malloc(sizeof(LNode));//为新结点申请空间
s->data=x;
r->next=s;//新结点给尾结点的next指针
r=s;//r要指向新的尾部
scanf("%d",&x);
}
r->next=NULL;//让尾结点的next为null
}
//打印
void print_list(LinkList L)
{
L=L->next;
while(L !=NULL )
{
printf("%3d",L->data);
L=L->next;
}
printf("\n");
}
//找链表中间结点,并设置好L2链表
void find_middle(LinkList L,LinkList &L2)
{
L2=(LinkList) malloc(sizeof(LNode));//第二条链表的头结点
LinkList pcur,ppre;//双指针遍历,考研初试常考
// pcur每次走两步,ppre每次走一步,当pcur到尾部时,ppre到达中间位置
ppre=pcur=L->next;
//pcur指向最后一个为null,结束循环
while (pcur)
{
pcur=pcur->next;
if(pcur==NULL)//为了防止pcur为空
{
break;
}
pcur=pcur->next;
if(pcur==NULL)//为了使得偶数个,ppre依然指向中a1-a6中的a3
{
break;
}
ppre=ppre->next;
}
L2->next=ppre->next;//由L2头结点指向后面一半链表
ppre->next=NULL;//前一段链表尾结点的next要为空
}
//链表反转
void reverse(LinkList L2)
{
LinkList r,s,t;//r,s,t分别指向链表前三个结点
r=L2->next;
if(r==NULL)
{
return;//链表为空
}
s=r->next;
if(s==NULL)
{
return;//链表只有一个结点
}
t=s->next;
while(t)
{
s->next=r;//原地逆置
r=s;//以下三句是3个指针向后走一步
s=t;
t=t->next;
}
s->next=r;
L2->next->next=NULL;//逆置后,链表的第一个结点要为null
L2->next=s;//逆置后,s是链表的第一个结点,L2指向他
}
//合并两个链表
void merge(LinkList L,LinkList L2)
{
LinkList pcur,p,q;
pcur=L->next;//pcur始终指向组合后新链表的链表尾
p=pcur->next;//p指向L待放入结点(p遍历L链表)
q=L2->next;//q指向L2待放入结点(q遍历L2链表)
while(p!=NULL && q!=NULL)
{
pcur->next=q;
q=q->next;//指向下一个
pcur=pcur->next;
pcur->next=p;
p=p->next;//指向下一个
pcur=pcur->next;
}
//任何一个链表都可能剩余一个结点,放进来即可
if(p!=NULL)
{
pcur->next=p;
}
if(q!=NULL)
{
pcur->next=q;
}
}
int main() {
LinkList L;//链表头,是结构体指针类型
list_tail_insert(L);
print_list(L);//打印
printf("--------------------------\n");
//寻找中间结点,并返回第二条链表
LinkList L2=NULL;
find_middle(L,L2);//只有一个结点的时候,L2是没有结点的
print_list(L);
print_list(L2);
printf("--------------------------\n");
reverse(L2);
print_list(L2);
printf("--------------------------\n");
merge(L,L2);
free(L2);
print_list(L);
return 0;
}
测试结果
1 2 3 4 5 6 9999
1 2 3 4 5 6
--------------------------
1 2 3
4 5 6
--------------------------
6 5 4
--------------------------
1 6 2 5 3 4
(3)
- find_middle函数有一个while循环,pcur每次移动两个结点,循环的次数是n/2,忽略系数,所以时间复杂度是O(n)。
- reverse函数只遍历了L2链表,遍历长度是n/2,忽略系数,所以时间复杂度是O(n)。
- merg函数while循环遍历次数是n/2,忽略系数,所以时间复杂度是O(n)。
综上,三个函数总运行次数是1.5n,忽略系数,所以时间复杂度是O(n)。
参考答案:
- 参考王道视频课程