链表排序

一直以来算法题刷的比较少,算法这块算是我的弱项。前几天,看到一个挺有意思的链表题

原题

原题是这样的,给一个链表 [1,2,3,4,5] 然后转换成 [5,3,1,2,4] 这种格式
找一下规律,其实就是第一个先拿第一个元素,然后把第二个元素放在新的链表后面,第三个元素放在新的链表前面,然后第四个放在新链表的后面,第五个放在新链表前面,以此类推。

其实就是一个链表头插和尾插的结合嘛

直接上代码

package main

import (
    "fmt"
)

type Node struct {
    Next *Node
    Val  int
}

//通过数组生成链表
func Make(nums []int) *Node {
    head := &Node{}
    move := head
    for _, num := range nums {
        t := &Node{
            Val: num,
        }
        move.Next = t
        move = move.Next
    }
    return head.Next
}

//打印链表
func Print(list *Node) {
    for h := list; h != nil; h = h.Next {
        fmt.Printf("%d ", h.Val)
    }
    fmt.Println()
}

func main() {
    nums := make([]int, 5)
    for i := 1; i <= 5; i++ {
        nums[i-1] = i
    }
    nodes := Make(nums)
    Print(nodes)
    nodes = TurnList(nodes)
    Print(nodes)
}

//转换链表
func TurnList(list *Node) *Node {
    i := 0
    h := list
    t := list
    list = list.Next
    for list != nil {
        if i%2 == 0 {
            t.Next = list
            list = list.Next
            t = t.Next
            t.Next = nil
        } else {
            x := list.Next
            list.Next = h
            h = list
            list = x
        }
        i++
    }
    return h
}

初始链表如图:


变换过程:



就是样根据奇偶分别头插和尾插就能完成
这么简单当时就是没写出来。 淦

只写这一个也抬不过瘾了,再来一个链表排序的 leetcode 148

链表排序

排序的方法有好多,但是我感觉适合用于链表的是 插入排序归并排序 尤其是归并排序,简直就是为链表定做的。

先来一个简单的,插入排序吧

插入排序的思想就是选择一个基准,然后把整个序列中的比自己小的放到基准的前面,用数组的形式还是很好理解和操作的。但是链表不能随机访问,所以不能用数组那样的插入排序了,需要做一下变换,看起来有点像是选择排序和插入排序的结合。
不废话了,直接上代码,插入排序:

func InsertSort(list *Node) *Node {
    head := &Node{
        Next: list,
    }
    h := head
    for h.Next != nil {
        t := h
        for t.Next != nil {
            if t.Next.Val < h.Next.Val {
                x := t.Next
                t.Next = t.Next.Next
                x.Next = h.Next
                h.Next = x
            } else {
                t = t.Next
            }
        }
        h = h.Next
    }
    return head.Next
}

无序的链表

排序过程


图中有个迷惑的地方,就是蓝色的线是 next 指针的指向,就是一开始的时候,head h t 的next指针指向 3
也就是 h t自身是指向 head的,head是一开始的时候,新建的一个临时结点,因为单向链表无法向前查找,只能向后查找,所以要用 next 指针。
同样,在使用 th 指针的时候,也都是使用 next 用来判断。
比较逻辑 t.Next.Val < h.Next.Valtnext3hnext2 时,满足条件,所以交换 23
如此往复,当 t 走完一趟后,链表中的第一个结点就是最小的了。
t 走完一趟后,h指针要后移一个。以此类推,直到 ht 都走完后,就完成了排序。

这个排序方法用的是插入排序的思想加上一些选择排序的方法完成的,时间复杂度还是O(n²)。是稳定的排序。

归并排序

相比插入排序和选择排序,归并排序就要复杂一些了

归并排序的思想是分治法,把整个序列拆分,当拆分到不能再拆分时,子序列就是有序的了。也就是当序列长度是1的时候,序列就是有序的。然后在把子序列有序的合并起来。

归并排序简直就是为了链表排序而生的。如果用数组排序,拆分和重组还是比较复杂的,链表就简单多了。说实话,数组的归并排序我都不会写。

线上代码,归并排序

func MergerSort(list *Node) *Node {
    if list == nil || list.Next == nil {
        return list
    }

    fast := list.Next
    slow := list
    for fast != nil && fast.Next != nil {
        fast = fast.Next.Next
        slow = slow.Next
    }

    h1 := MergerSort(slow.Next)
    slow.Next = nil
    h2 := MergerSort(list)

    return Merge(h1, h2)
}

func Merge(list1, list2 *Node) *Node {
    head := &Node{}
    h := head
    for list1 != nil && list2 != nil {
        if list1.Val < list2.Val {
            h.Next = list1
            list1 = list1.Next
        } else {
            h.Next = list2
            list2 = list2.Next
        }
        h = h.Next
    }
    if list1 != nil {
        h.Next = list1
    } else {
        h.Next = list2
    }
    return head.Next
}

归并排序有两个部分,一个是拆分,另一个是合并。

  1. 拆分
    拆分是二分法,也就是每次从中间分开。链表中找到中间位置,最方便的就是快慢指针了,就是一个指针每次移动一个位置,指针每次移动两个位置。当快的指针移动到最后的时候,慢的指针正好移动到链表的中间位置。递归拆分,直到链表长度是1的时候,就完成了拆分。

  2. 合并
    合并的过程其实就是合并两个有序链表。leetcode 21 很简单没什么需要解释的。

简单的把排序过程花了出来。

整个归并排序,不好理解的地方就是递归拆分的过程,其实也不难理解,就是能拆的情况下就一直拆,直到不能拆了为止。

    fast := list.Next
    slow := list
    for fast != nil && fast.Next != nil {
        fast = fast.Next.Next
        slow = slow.Next
    }

当一次拆分完成时,slow 指针指向的就是链表的中间位置,所以 slownext 就是链表的后半部分了,list 指针就是链表的前半部分。

拆分的过程中有个地方需要注意,就是断链,slow.Next = nil 因为在用快慢指针找到链表的中间位置时,并没有把链表切分开,需要手动把链表断开,要不然从头开始遍历,还是能遍历整个链表的。

总结

链表的问题其实没有没有特别难的算法,基本都是一些基础的操作。链表难的地方是因为内存地址不连续,不像数组那样可以随机访问,只能通过指针操作,理解起来没有那么直观所以会觉难。遇到链表问题就画图,基本上图画出来了,问题也基本能解决了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容