#include <stdio.h>
#include <stdlib.h>
typedef int ElemType;
typedef struct Lnode
{
ElemType data;// int型数据域
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;
r=s;//r要指向新的尾部
scanf("%d",&x);
}
r->next=NULL;//尾节点的next为NULL
}
//------链表打印-----------
void print_list(LinkLIst L) //只读不改动 不需要C++引用
{
L=L->next;
while(L!=NULL)
{
printf("%3d",L->data);
L = L->next;
}
printf("\n");
}
//----注意!408初试并不考察以上插入以及打印结构体 是对关键函数的考察-----------
//断链
void find_middle(LinkLIst L,LinkLIst &L2)
{
L2=(LinkLIst)malloc(sizeof(LNode));//(第二条链表的头节点) 为新结点申请头节点空间,头指针指向头节点
LinkLIst pcur,ppre;//双指针遍历,考研初始常考
ppre=pcur=L->next;
while (pcur)
{
pcur=pcur->next;
if(NULL==pcur)//为了防止pcur为NULL
{
break;
}
pcur=pcur->next;
if(NULL==pcur)//为了使得偶数个,ppre依然指向a1,a2,到a6中的a3结点
{
break;
}
ppre=ppre->next;
}
L2->next=ppre->next;//由L2头节点指向后面一半链表
ppre->next=NULL;//前一半链表的最后一个结点,next要为NULL
}
//逆转
void reverse(LinkLIst L2)//逆转L2这个结构体 头指针并不发生改变 故不需要引用
{
LinkLIst r,s,t;
r=L2->next;
if(NULL==r)
{
return; //链表为空
}
s=r->next;
if(NULL==s)
{
return; //链表只有一个结点
}
t=s->next;
while (t)//如果t不为空
{
s->next=r;//t往后移的前提
r=s;//以下三句是三个指针同时往后走一步
s=t;
t=t->next;
L2->next->next=NULL;//逆置后,链表第一个结点的next要为NULL
L2->next=s;//s才是新链表的表头
}
s->next=r;//t为NULL后倒数第二个还需要指向前一个完成最后的逆置
}
//合并链表
void merge(LinkLIst L,LinkLIst L2)
{
LinkLIst pcur,p,q;
pcur=L->next;//pcur始终走向组合后链表的表尾
p=pcur->next;//p用来遍历L1链表
q=L2->next;//q指向L2第一个结点,q用来遍历L2链表
while(p!=NULL&&q!=NULL)
{
pcur->next=q;
q=q->next;//指向下一个 链表L2
pcur=pcur->next;
pcur->next=p;
p=p->next;//指向下一个 链表L1
pcur=pcur->next;
}
//任何一个链表都可能剩余一个结点,放进来即可
if(p!=NULL)
{
pcur->next=p;
}
if(q!=NULL)
{
pcur->next=q;
}
}
int main() {
LinkLIst L;//L是链表头指针,是结构体指针类型 //考试可写L已初始化好 备注a1-an
list_tail_insert(L); //考试可省略
print_list(L); //考试可省略
//-------以下为必要内容--------------
//寻找中间结点,并返回第二条链表
LinkLIst L2=NULL;
find_middle(L,L2);//只有一个结点时,L2中是没有结点的
printf("-----------------------------\n");
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;
}
判断其时间复杂度:
真题代码的三个函数find_middle()、reverse()、merge()的时间复杂度都为O(n/2),主要原因在于它们都是对链表进行遍历的操作,且每次遍历都跳过一些节点,使得实际遍历的节点数量为链表总节点数的一半。下面分别解释三个函数的时间复杂度:
当分析这三个函数的时间复杂度时,我们可以从每个函数的核心遍历部分入手:
-
find_middle函数:
- 在find_middle中,使用了两个指针pcur和ppre,其中pcur每次移动两步,ppre每次移动一步。这样,当pcur到达链表末尾时,ppre正好到达链表的中间位置。由于每一步都在遍历节点,因此遍历的总次数约为链表长度的一半。因此,时间复杂度为O(n/2)。
-
reverse函数:
- reverse函数采用了三个指针r、s、t来逆转链表。在while循环中,每次都将s的next指向r,然后三个指针依次往后移动。因为每次循环都处理两个节点,所以逆转整个链表的总次数约为链表长度的一半。因此,时间复杂度为O(n/2)。
-
merge函数:
- merge函数通过两个指针p和q同时遍历两个链表,将节点逐个合并到一个新链表中。在每个循环中,都是将一个节点从链表中取出,并加入到新链表中。由于每次循环都处理两个节点,整个合并过程的总次数约为链表长度的一半。因此,时间复杂度为O(n/2)。
综上所述,这三个函数的核心思想是通过巧妙的指针移动和逻辑设计,在遍历链表时跳过一些节点,从而实现对链表的操作,使得时间复杂度都保持在O(n/2)的水平。这种设计有效地减少了遍历的总次数,提高了算法的效率。
上面三个函数总的运行次数是1.5n,忽略首项系数,因此时间复杂度是O(n)