设线性表L = (a1,a2,a3,...,an)采用头结点的单链表保存,链表中的结点定义如下:
typedef struct node
{
int data;
struct node *next;
}NODE;
请设计一个空间复杂度为O(1)且时间上尽可能高效的算法,重新排列L中的各结点,得到线性表L` = (a1,an,a2,..,).要求:
(1)给出算法的基本思想
(2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释
(3)说明你设计的算法时间复杂度。
1.算法的基本设计思想
先观察L = (a1,a2,a3,...,an)和L` = (a1,an,a2,..,),发现L`是由L摘取第一个元素,再摘取倒数第一个元素依次合并而成的。为了方便链表后半段取元素,需要将L后半段原地逆置【题目要求空间复杂度为O(1),因此不能够借助栈】,否则每取到一个结点都需要遍历一次链表。(1)先找出链表的中间结点,为此设置两个指针p和q,指针p每走一步,指针q每次走两步,当指针q到达链尾时,指针p正好在链表的中间结点;(2)然后将L的后半段结点原地逆置。(3)从单链表前后两端依次各取一个结点,按压求重拍。
2.算法实现
void change_List(NODE *h)
{
NODE *p, *q, *r, *s;
p = q = h;
while(q->next != NULL)//寻找中间节点
{
p = p->next;//p走一步
q = q->next;
if(q->next != NULL)//判断后面是否为空
{
q = q->next;//q走两步
}
}
q = p->next;//p所指结点为中间结点,q为后半段链表的首节点
p->next = NULL;//直接将链表分为两部分
//将链表后半段倒置,就是换一个方向
while(q != NULL)
{
r = q->next;//链表的后半段:r指向q指向结点的后面一个结点
q->next = p->next;//第一次的时候p->next = NULL,所以此时后半段链表的首节点与第二个结点连接断开
p->next = q;//将p挂在p的后面在结合下面一个语句就可以循环你,使链表的后半段的结点连接顺序改变,倒置
q = r;
}
s = h->next;//s指向前半段的第一个数据结点,即插入点
q = p->next;//q指向后半段的第一个数据结点
p->next = NULL;
while(q != NULL)
{
r = q->next;//将r指向后半段q指向结点的下一个结点
q->next = s->next;//改变q指向结点的后继节点,使后继节点变成s指向结点的后继节点
s->next = q;//使s指向结点的后继节点变成q指向的那个结点
s = q->next;//使s指向q指向结点的后继节点
q = r;//使q指向r指向的那个结点,以构成循环
}
}
演示过程
3.第一步找中间结点的时间复杂度为O(n),第二步逆置的时间复杂度为O(n),第三步合并链表的时间复杂度为O(n),所以该算法的时间复杂度为O(n)
完整代码
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
typedef struct node
{
int data;
struct node *next;
}NODE;
//函数说明
NODE *creat_List(void);
void traverse_List(NODE *h);
void change_List(NODE *h);
int main(void)
{
NODE *h = NULL;
h = creat_List();
printf("链表输出结果:\n");
traverse_List(h);
change_List(h);
printf("改变之后链表的值为:\n");
traverse_List(h);
return 0;
}
//创造一个链表
NODE *creat_List(void)
{
int len;//用来存放有效结点的个数;
int i;
int val;//用来临时存放有效节点的值;
NODE *h = (NODE*)malloc(sizeof(NODE));
if(NULL == h)
{
printf("动态内存分配失败,结束程序!");
exit(-1);
}
NODE *t = h;
t->next = NULL;
printf("链表结点的个数:len = ");
scanf("%d",&len);
for(i = 0; i<len;++i)
{
printf("请输入第%d个结点的值:",i+1);
scanf("%d",&val);
NODE *pNew = (NODE*)malloc(sizeof(NODE));
if(NULL == pNew)
{
printf("结束程序!\n");
exit(-1);
}
pNew->data = val;
t->next = pNew;
pNew->next = NULL;
t = pNew;
}
return h;
}
//遍历输出
void traverse_List(NODE *h)
{
NODE *p = h->next;
while(NULL != p)
{
printf("%d ",p->data);
p = p->next;
}
printf("\n");
return;
}
void change_List(NODE *h)
{
NODE *p, *q, *r, *s;
p = q = h;
while(q->next != NULL)//寻找中间节点
{
p = p->next;//p走一步
q = q->next;
if(q->next != NULL)//判断后面是否为空
{
q = q->next;//q走两步
}
}
q = p->next;//p所指结点为中间结点,q为后半段链表的首节点
p->next = NULL;//直接将链表分为两部分
//将链表后半段倒置,就是换一个方向
while(q != NULL)
{
r = q->next;//链表的后半段:r指向q指向结点的后面一个结点
q->next = p->next;//第一次的时候p->next = NULL,所以此时后半段链表的首节点与第二个结点连接断开
p->next = q;//将p挂在p的后面在结合下面一个语句就可以循环你,使链表的后半段的结点连接顺序改变,倒置
q = r;
}
s = h->next;//s指向前半段的第一个数据结点,即插入点
q = p->next;//q指向后半段的第一个数据结点
p->next = NULL;
while(q != NULL)
{
r = q->next;//将r指向后半段q指向结点的下一个结点
q->next = s->next;//改变q指向结点的后继节点,使后继节点变成s指向结点的后继节点
s->next = q;//使s指向结点的后继节点变成q指向的那个结点
s = q->next;//使s指向q指向结点的后继节点
q = r;//使q指向r指向的那个结点,以构成循环
}
}
以六个整数型结点为例子结果为: