前言
第一次看到这题时两眼一黑,因为做惯了排序和链表大多都是带头链表且没有限制额外空间,这题叠了两个buff,很担心出题老头的精神状态。
一番搜索,都没有找到一个最优解,但是溯源到了一个母题,来自北大2015年。
算法代码
//不带头,题源北大2015
//直接插入排序
//list是不带头结点的线性链表,链表结点构造为data和link两个域,data是数据域,link是指针域。
//本算法将该链表按结点数据域的值的大小,从小到大重新链接。
LinkedList LinkListSort(LinkedList &list){
p=list->link; //p是工作指针,指向待排序的当前元素。
list->link=null; //将原来的链表当做待插入的有序链表的头指针
while(p!=null){
r=p->link; //r是p的后继。
q=list;
if(q->data > p->data) { //处理待排序结点p比第一个元素结点小的情况。//更换头指针
p->link=list;
list=p; //链表指针指向最小元素,做有序链表的头指针
}
else{ //找最小值
while(q->link!=null&&q->link->data < p->data) q=q->link;
//q指针遍历链表,寻找有序链表尾指针p真正对应的元素
p->link=q->link; //将当前排序结点链入有序链表中。
q->link=p;
}
p=r; //p指向下个待排序结点。
}
}
算法思想与过程模拟
算法的运行主要考虑到:
①第一个数不是最小的;
②第二个数大于第一个数或小于第一个数;
③后一个数大于前一个数;
④后一个数小于前一个数;
⑤排序过程中不能断,所以需要一个r。
举例:有一个链表 2(L)→1→3→5→4
定义了一个p(工作指针),一个r(防止断链,始终在p之后),一个q(用于链接新的表)
代码行【6】p指向L的next,即1
【7】L(2)摘出来,单独成链
【8、9】进入p的大循环,r指向p后续(3)
【10】q指向L(2)
【11】第一次小循环,q(2)大于p,即第一个点大于待处理点p
【12】p(1)伪头插到L(2),此时新链为: 1(p)→2(L)
【13】L移到p(1),即保证L始终指向最小值(成为头),此时:1(p,L)→2
【21】p回到r,下一个是3
【9】r指向5
【10】q指向L(1)
【11】1p小于3q,跳到【15】
【16】判断q(1)next不为空且next小于p(3),的确,2小于3,q指向2
【18】p(3)指向q(2)的next(此时为NULL)
【19】q(2)链接p(3),此时:1(L)→2(q)→3(p)
【21、8、9、10】r指向4,p指向5,q指向1
【11】1小于5,5大于3,一样的,插到3之后
【21、8、9、10】r指向NULL,p指向4,q指向1
【11】1小于4,4大于5
【16】q遍历到3时,3的next(5)大于p,跳出while
【18】这一行的作用来了,p(4)next到q的next(5)
【19】q连接p,此时:1(L)→2→3(q)→4(p)→5
【21】【8】完成,跳出
附上一个带头结点的,按照顺序输出:
void fun(LinkList &head){
while(head->next){ //循环到仅剩头结点
*pre=head; //pre 为元素最小值结点的前驱结点的指针
*p=pre->next; //p 为工作指针
while(p->next){
if(p->next->data < pre->next-data)
pre=p; //记住当前最小值结点的前驱
p=p->next;
}
print(pre->next->data); //输出元素最小值结点的数据
u=pre->next; //删除元素值最小的结点,释放结点空间
pre->next=u->next;
free(u);
}
free(head);
}