题目:设线性表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…)
关键字:带头结点单链表+穿插+逆置
思路 :为了达到穿插+逆置的效果,我们选择分段+逆置+插入
先观察L=(a1,a2,a3,…an-2,an-1,an)和L’=(a1,an,a2,an-1,a3,an-2,…)
发现L’是由L摘取第一个元素,再摘取倒数第一个元素…依次合并而成的。
为了方便链表后半段取元素,需要先将L后半段原地逆置【题目要求空间复杂度为O1,不能借助栈】,否则每取最后一个结点都需要遍历一次链表。
1)先找出链表L的中间结点,利用速度差产生路程差法
ps:顺序表就是“直接算算地址”法了
为此设置两个指针p和q,指针p每次走一步,指针q每次走两步
当指针q到达链尾时,指针p自然就在链表的中间结点
2)将L的后半段结点原地逆置
需要变量:q的后继结点指针r,负责将“逆置指针”的工作
3)从单链表前后两段中依次各取一个结点,按要求重排。
需要变量:
void change_list(Node*L)
{ Node*p,*q,*r,*s;//
p=q=L;
while(q->next!=NULL)//寻找中间结点
{ p=p->next;//p走一步
q=q->next;//q走一步
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反转指针的指向时需要防止断链
q->next=p->next;//由于就近,利用刚好处于中间结点的p来帮助q反转指向
p->next=q;//通过循环遍历让p(前半段最后一个元素,即中间结点)从指向后半段第一个结点逐步后移,最终指向后半段最后一个元素(表尾结点)
q=r;//利用后驱指针r来防止q回不到后半链
}
s=L->next;//s为前半段链表的首结点(因为此时p作为中间结点指针需要为添加后半段指针做准备)
q=p->next;//q为后半段指针
p->next=NULL;//断开前半段链表
while(q!=NULL)
{ r=q->next;//r为后半段链表的后驱指针
q->next=s->next;//将后半段链表的结点链接到前半段链表中
s->next=q;//
s=q->next;//s遍历(一次前进两个结点)
q=r;//q遍历
}
}