题目
给定一个链表的头节点,和一个int,int的值是链表中一个节点的值,按小于int在左边,等于int在中间,大于int在右边将链表重新链接
分析:
链表的荷兰国旗问题,
方法1 :将链表的节点一次加入数组,以荷兰国旗的解法将链表排序,输出时一次链接链表元素
方法2:类似分区的思想,只是将区间分别用小于头结点(shead),小于尾节点(stail),等于头结点(ehead),等于尾节点(etail),大于头结点(mhead),大于尾节点(mtail)六个指针来划分三个分区,将符合条件的节点一次链接到对应到分区,最后将三个分区首尾相连
代码:
package main
import (
"fmt"
// "math"
// "sort"
// "time"
)
//链表本身,包含长度,头结点和尾节点,对于一个双向链表,
//我们只关注它的haea和tail,head和tail是一个个具体的节点
type LinkedList struct {
len int
head *LinkedNode
tail *LinkedNode
}
//链表中的一个节点的结构,包含节点的数据,前向指针,后向指针,指向的也是一个节点本身
type LinkedNode struct {
data int
next *LinkedNode
prev *LinkedNode
}
// type Data struct{
// key string
// data int
// }
//所有的操作都基于指针
func NewLinkedList() *LinkedList {
return &LinkedList{
len: 0,
head: nil,
tail: nil,
}
}
//追加一个节点,链表没有头结点就把自己设为链表的头结点,头尾指针都指向自己
//有头结点就把自己放在最后,1先把自己的prev指向原来的tail,2把原来的tail的next指向自己,3把链表的tail设为自己
func (ll *LinkedList)Add(node *LinkedNode) {
// node := &LinkedNode{
// data: data,
// }
// fmt.Printf("hhhhhhead%v\n", ll.head)
if ll.head==nil{
ll.tail=node
ll.head=node
}else{
// fmt.Printf("tttttail%v\n", ll.tail)
node.prev=ll.tail
ll.tail.next=node
ll.tail=node
}
ll.len++//记得更新长度
}
func partition(list []*LinkedNode,num int) []int{
more:=len(list)
less:=-1
index:=0
for index < more{
// fmt.Println("here")
if list[index].data<num{
fmt.Printf("exchange idex %v less %v ",index+1,less+1)
swap(list,index,less+1)
less++
index++
//小于去必然小于等于index,将一个小于num的的数放进小于区,不会引起小于区的混乱,
//小于去已经是检查过的区域,必然符合规则,不用重新检测index位置
}else if list[index].data>num{
fmt.Printf("exchange idx %v more %v ",index,more-1)
swap(list,index,more-1)
more--//大于区的数未知,放到index位置的话可能引起混乱,所以index不推进,从当前index继续检测
}else{//等于什么都不做,,所以不会引起任何混乱,直接推进index检查下一位
index++
}
fmt.Println(index)
fmt.Println(list)
}
fmt.Println(less+1,more-1)
result:=[]int{less+1,more-1}
return result
}
func swap(list []*LinkedNode,m,n int){
tmp:=list[m]
list[m]=list[n]
list[n]=tmp
}
//实现1
//将链表放进list进行partion分区,小于的在左边等于中间大于右边(荷兰国旗分区)
//将分好区的节点一次取出,取出的时候挂上next节点
func partitionlist(head *LinkedNode,num int){
tmp:=[]*LinkedNode{}
i:=0
node:=head
for node !=nil{
i++
tmp=append(tmp,node)
node=node.next
}
partition(tmp,num)
fmt.Printf("%#v\n",tmp)
//先将0节点和1节点连接好,下面的遍历直接从1节点开始,从1开始是为了方便指定前后指针,如果是单向链表就不用
tmp[0].next=tmp[1]
tmp[0].prev=nil
tmp[1].prev=tmp[0]
for t:=1;t<i;t++{
if t==i-1{
tmp[t].next=nil
}else{
tmp[t].next=tmp[t+1]
tmp[t+1].prev=tmp[t]
}
// fmt.Println(tmp[t])
// fmt.Println(tmp[t].next)
// fmt.Println("___________________________")
}
for t:=0;t<i;t++{
fmt.Println(tmp[t].prev)
fmt.Println(tmp[t])
fmt.Println(tmp[t].next)
fmt.Println("___________________________")
}
}
//实现2
//借助分区思想,但分区是借助六个指针,小于区头指针shead,小于区尾指针stail,等于区头指针ehead,大于区头指针mhead以此类推
//遍历链表,对比值大于就放进大于分区,小于放进小于分区,等于放进等于分区(实际是靠指针实现,分区头尾指针标记对应的分区的地址,降低空间复杂度)
//最后将小于区尾指针指向等于区头指针,等于区尾指针指向大于区头指针,将三个分区按小-中-大首尾相连,(这里我简化成单向链表了)
func partitionlist1(head *LinkedNode,num int) *LinkedNode{
var shead *LinkedNode
var stail *LinkedNode
var ehead *LinkedNode
var etail *LinkedNode
var mhead *LinkedNode
var mtail *LinkedNode
node:=head
for node !=nil{
// fmt.Println(node)
// node.next=nil
node.prev=nil
if (*node).data<num{
if shead !=nil{
stail.next=node
stail=node
}else{
shead=node
shead.prev=nil
stail=node
// shead.next=stail
//这里为什么不再需要挂后节点,因为初始head就等于tail,而且指定了tail的next。也就相当于指定了head的next
//这里为什么不再需要挂后节点,因为初始head就等于tail,而且指定了tail的next。也就相当于指定了head的next
}
}else if (*node).data>num{
if mhead !=nil{
mtail.next=node
mtail=node
}else{
mhead=node
mhead.prev=nil
mtail=node
// mhead.next=mtail
}
}else{
if ehead !=nil{
etail.next=node
etail=node
}else{
ehead=node
ehead.prev=nil
etail=node
// ehead.next=etail
}
}
node=node.next
}
//判断每个区是否为空,然后过滤掉为空的区后链起来
if stail != nil {
if ehead !=nil{//小于区不为空,等于区不为空,大于区不用判断了,直接链起来
stail.next=ehead
etail.next=mhead
mtail.next=nil//初始的节点都有前后指针,这里要将做后一个节点的后指针置空
}else{//小于区不为空,等于区为空,大于区不用判断,直接将小于区与大于区链起来
if mtail != nil{//大于区不为空,等于区为空,将小于区与大于区链起来,将大于区tail的next置空
stail.next=mhead
mtail.next=nil
}else{
stail.next=mhead
}
}
}else{//小于区为空
if ehead != nil{
if mtail !=nil{
etail.next=mhead
mtail.next=nil
return ehead
}
}else{//小于区为空,等于区为空,直接返回大于区,不管是不是空
return mhead
}
}
return shead
}
func main(){
var head LinkedNode
head.data=18
var l1 LinkedNode
l1.data=5
var l2 LinkedNode
l2.data=20
var l3 LinkedNode
l3.data=4
var l4 LinkedNode
l4.data=3
var l5 LinkedNode
l5.data=8
var l6 LinkedNode
l6.data=12
var l7 LinkedNode
l7.data=12
list := NewLinkedList()
list.Add(&head)
list.Add(&l1)
list.Add(&l2)
list.Add(&l3)
list.Add(&l4)
list.Add(&l5)
list.Add(&l6)
list.Add(&l7)
num:=17
node:=partitionlist1(&head,num)
fmt.Printf("return %#v\n" ,node)
for node !=nil{
fmt.Println(node)
// fmt.Println(node.next)
fmt.Println("+-----------------------+")
node=node.next
}
}
}
需要注意的点:
1 链表相关的操作,与指针相关的,最好都用指针,尤其是方法2中实际就是依靠指针,
2 方法2 在第一次将一个节点加到分区的时候,头结点和尾节点都指向它,头尾指针仅仅是指针而已,指向的是同一个值,第二次接入节点的时候,将节点加到tail.next,因此产生了链接关系,不需要再次链接
3 初始链表中每个节点都有前后两个指针coding到时候要考虑将分好区的链表的UI后一个节点的为指针置空,否则会出现循环链表(前节点不考虑)