算法导论第六章-堆排序(五)

6.5-1~6.5-5:略 可以参考最小优先队列的golang实现

6.5-6 在HEAP-INCREASE-KEY的第五行的交换操作中,一般需要通过三次赋值来完成。想一想如何利用INSERTTION-SORT内循环部分的思想,只用一次赋值就完成这一交换操作?
答:伪代码如下

while i >1 and A[PARENT(i)] < key
        A[i] = A[PARENT(i)]
         i = PARENT(i)
end while
A[i] = key

6.5-7 试说明如何利用优先队列来实现一个先进先出队列,以及如何使用优先队列来实现栈。
答:栈的定义:
实现:在优先队列的数据结构中增加个count的字段,这个字段表示的是当前这个队列里面的元素个数。然后每次添加新元素的时候,用这个count值来表示它的key,这样就能实现先进后出了。

6.5-8 HEAP-DELETE(A,i)操作能够将结点i从堆A中删除。对于一个包含n个元素的堆,请设计一个能够在O(lgn)时间内完成的HEAP-DELETE操作
答:伪代码:

A[i] = A[A.heapSize]
A.heapSize  = A.heapSize -1
MAX-HEAPIFY(A,i)

6.5-9 请设计一个时间复杂度为O(nlgk)的算法,它能够将k个有序链表合并为一个有序链表,这里n是所有输入链表包含的总的元素个数。
答:这里要分为三大部分,第一部分是单链表的golang实现,代码为

package main

import (
    "errors"
    "fmt"
)

//Element 定义元素类型
type Element int

//LinkNode 定义结点
type LinkNode struct {
    Data Element
    Next *LinkNode
}

//LinkNodeInterface 函数接口
type LinkNodeInterface interface {
    Add(data Element)                     //后面添加
    Delete(index int) (Element, error)    //删除指定index位置元素
    Insert(index int, data Element) error //在指定index位置插入元素
    GetLength() int                       //获取长度
    Search(data Element) int              //查询元素的位置
    GetData(index int) (Element, error)   //获取指定index位置的元素
}

//GetLength 获取长度
func (head *LinkNode) GetLength() int {
    return int(head.Data)
}

//Add 添加数据
func (head *LinkNode) Add(data Element) {
    point := head
    for point.Next != nil {
        point = point.Next
    }
    node := new(LinkNode)
    node.Data = data
    point.Next = node
    head.Data++
}

//Delete 删除结点
func (head *LinkNode) Delete(index int) (Element, error) {
    if index < 0 || index >= head.GetLength() {
        return 0, errors.New("please check index")
    }
    point := head
    for i := 0; i < index; i++ {
        point = point.Next
    }
    data := point.Next.Data
    point.Next = point.Next.Next
    head.Data--
    return data, nil
}

//Insert 插入
func (head *LinkNode) Insert(index int, data Element) error {
    //检验index合法性
    if index < 0 || index >= head.GetLength() {
        return errors.New("please check index")
    }
    point := head
    for i := 0; i < index; i++ {
        point = point.Next
    }
    node := new(LinkNode)
    node.Data = data
    node.Next = point.Next
    point.Next = node
    head.Data++
    return nil
}

//Search 搜索 -1表示不存在该元素
func (head *LinkNode) Search(data Element) int {
    point := head.Next
    index := 0
    for point != nil {
        if point.Data == data {
            fmt.Println(data, "exist at", index, "th")
            return index
        }
        index++
        point = point.Next
    }
    return -1
}

//GetData 获取data
func (head *LinkNode) GetData(index int) (Element, error) {
    point := head
    if index < 0 || index >= head.GetLength() {
        return 0, errors.New("please check index")
    }
    for i := 0; i <= index; i++ {
        point = point.Next
    }
    return point.Data, nil
}

//Traverse 遍历
func (head *LinkNode) Traverse() {
    point := head
    for point.Next != nil {
        point = point.Next
        fmt.Println(point.Data)
    }
    fmt.Println("Traverse OK!")
}

然后是针对链表改造过的最小堆,原来的最小堆只是简单的针对整数的。

package main

// MinHeap 最小堆的结构
type MinHeap struct {
    heapSize int
    heap     []*LinkNode
}

// LEFT 返回子树左边的元素
func (A *MinHeap) LEFT(i int) int {
    return i << 1
}

// RIGHT 返回子树右边的元素
func (A *MinHeap) RIGHT(i int) int {
    return (i<<1 + 1)
}

// PARENT 返回父结点
func (A *MinHeap) PARENT(i int) int {
    return i >> 1
}

// MinHeapify 最小化堆
func (A *MinHeap) MinHeapify(i int) {
    smallest := i
    l := A.LEFT(i + 1)
    r := A.RIGHT(i + 1)
    if l <= A.heapSize && A.heap[l-1].Data < A.heap[i].Data {
        smallest = l - 1
    }
    if r <= A.heapSize && A.heap[r-1].Data < A.heap[smallest].Data {
        smallest = r - 1
    }
    if smallest != i {
        A.heap[smallest], A.heap[i] = A.heap[i], A.heap[smallest]
        A.MinHeapify(smallest)
    }
}

// BuildMinHeap 构建最小堆
func (A *MinHeap) BuildMinHeap() {
    for i := A.heapSize / 2; i >= 0; i-- {
        A.MinHeapify(i)
    }
}

// GetHeapSize 获得堆大小
func (A *MinHeap) GetHeapSize() int {
    return A.heapSize
}

// AlterHeapSize 更改堆大小
func (A *MinHeap) AlterHeapSize(i int) {
    A.heapSize = i
}

// GetElement 获得堆中的元素
func (A *MinHeap) GetElement(i int) *LinkNode {
    return A.heap[i]
}

// SetElement 更新堆中的元素
func (A *MinHeap) SetElement(i int, key *LinkNode) {
    A.heap[i] = key
}

// Swap 交换堆中的元素
func (A *MinHeap) Swap(i int, j int) {
    A.heap[i], A.heap[j] = A.heap[j], A.heap[i]
}

// Append 向堆中追加元素
func (A *MinHeap) Append(i *LinkNode) {
    A.heap = append(A.heap, i)
}

// NewMinHeap 最小里堆的构造函数
func NewMinHeap(heapSize int, a []*LinkNode) *MinHeap {
    minHeap := MinHeap{heapSize: heapSize, heap: a}
    minHeap.BuildMinHeap()
    return &minHeap
}

第三部分就是K路归并算法了,思想是将每个链表的第一个元素提取出来,初始化成一个最小堆,然后取出该最小堆中的第一个元素就是根节点,该节点的data便是最小的,然后将该节点插入到新的单链表中;插入完成后,这个根节点所在的链表的下一个元素,将该元素替代原来根节点在最小堆中的位置然后最小堆化根节点,然后取新最小化的最小堆的根节点,插入到新的链表中。如果某个链表取完后,则将最小堆的第一个元素和最后一个元素对换一下,再最小堆化一下,同时堆的大小减1;最后,当最小堆的大小为0时,新的归并后的链表也就完成了。
时间复杂度:因为有k个链表,总共有n个元素,因此发生了n次的最小堆化,时间复杂度为O(nlgk)

//KMerge K路归并算法:linkedListArr单链表的slice
func KMerge(linkedListArr []*LinkNode) *LinkNode {
    //l 是最后生成那个单链表
    l := LinkNode{Data: 0, Next: nil}
    //先把k个链表的第一个元素初始化成一个最小堆
    a := NewMinHeap(len(linkedListArr), linkedListArr)
    for a.GetHeapSize() > 0 {
        //最小堆的第一个元素肯定是最小的元素
        linkNode := a.heap[0]
        //fmt.Println(linkNode.Data)
        l.Add(linkNode.Data)
        if linkNode.Next != nil {
            //如果最小堆根元素的NEXT不为空,则用该链表下一个元素去替代根元素然后最小化堆
            a.SetElement(0, linkNode.Next)
            a.MinHeapify(0)
        } else {
            //如果NEXT为空指针,则将最小堆的最后一个叶节点和根节点交换,然后最小化堆的第一个
            a.heap[0], a.heap[a.GetHeapSize()-1] = a.heap[a.GetHeapSize()-1], a.heap[0]
            a.AlterHeapSize(a.GetHeapSize() - 1)
            a.MinHeapify(0)
        }
    }
    return &l
}

测试以及结果:

//第一个单链表
    a := LinkNode{Data: 0, Next: nil}
    a.Add(1)
    a.Add(4)
    a.Add(9)
    //第二个单链表
    b := LinkNode{Data: 0, Next: nil}
    b.Add(2)
    b.Add(3)
    b.Add(7)
    //第三个单链表
    c := LinkNode{Data: 0, Next: nil}
    c.Add(6)
    c.Add(8)
    c.Add(10)
    KMerge([]*LinkNode{a.Next, b.Next, c.Next}).Traverse()
1
2
3
4
6
7
8
9
10
Traverse OK!

思考题部分

6.1 我们可以通过反复调用MAX-HEAP-INSERT实现向一个堆中插入元素,考虑BUILD-MAX-HEAP的如下实现方式:

BUILD-MAX-HEAP'(A)
        A.heap-size = 1
        for i = 2 to A.length
              MAX-HEAP-INSERT(A,A[i])

a.当输入数据相同的时候,BUILD-MAX-HEAP和BUILD-MAX-HEAP'生成的堆是否总是一样?如果是,请证明;否则,请举出一个反例。
b.证明:在最坏的情况下,调用BUILD-MAX-HEAP'建立一个包含n个元素的堆的时间复杂度是O(nlgn)

答: a. 不是总是一样的。叶节点顺序会不同。反例:对于一个数组A{3,2,1,4,5},当我们运行BUILD-MAX-HEAP'时得到5,4,1,2,3;当我们运行BUILD-MAX-HEAP时,得到5,4,1,3,2
b.每次插入花费最多O(lgn),共有n次插入,所以时间复杂度为O(nlgn)

6-2 d叉堆与二叉堆很类似,但(一个可能的例外是)其中的每个非叶节点有d个孩子,而不是仅仅两个。
a.如何在一个数组中表示一个d叉堆
b.包含n个元素的d叉堆的高度是多少?请用n和d表示
c.请给出EXTRACT-MAX在d叉最大堆上的一个有效实现,并用d和n表示出它的时间复杂度。
d.给出INSERT在d叉最大堆的一个有效实现,并用d和n表示出它的时间复杂度。
e.给出INCREASE-KEY(A,i,k)的一个有效实现,并用d和n表示出它的时间复杂度。

答:a. PAARENT[i] = math.Floor((i+1)/d) child(k,i) = (i - 1) * d + 1 + k 其中i代表数组第i个元素,k表示第k个子元素
b.高度是math.floor(logd(n*(d-1)))
c.伪代码:

//EXTRACT-MAX
if A.heap-size <1 then
        error "heap underflow"
end if
max = A[1]
A[1] = A[A.heap-size]
A.heap-size --
DMAX-HEAPIFY(A,1)
//DMAX-HEAPIFY(A,i)
largest = i
for k = 1 to d do
        if CHILD(k,i) <= A.heap-size and A[CHILD(k,i) > A[i] then 
                largest  = CHILD(k,i)
        end if
end for
if largest != i then
        exchange A[i] with A[largest]
        DMAX-HEAPIFY(A,largest)
end if

其中DMAX-HEAPIFY(A,1)是一个不断迭代递归的过程,每一层都要和d个元素比较所以时间复杂度为dlogd(n)
d.伪代码为:

// INSERT
A.heap-size ++
A[A.heap-size] = key
i = A.heap-size
while i >1 and A[PARENT(i)] < A[i] do
        exchange A[i] with A[PARENT(i)]
        i = PARENT(i)
end  while

时间复杂度为O(logd(n))
e:伪代码为:

 if key < A[i] then 
        error "new key is smaller than current key"
end if
A[i] = key
while i >1 and A[PARENT(i)] < A[i] do
        exchange A[i] with A[PARENT(i)]
        i = PARENT(i)
end  while

时间复杂度为O(logd(n))

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

推荐阅读更多精彩内容

  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,321评论 8 265
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,600评论 18 139
  • 看了好多的电影,几乎都难逃一个信仰,难逃勇气与毅力。我们有信仰,不与现实里的困难妥协,愿意相信宇宙中深不可测的那一...
    敷小衍阅读 567评论 0 0
  • 治疗生活中苦难最灵的妙药,就是把自己变得优秀点。无论是网上热传的鸡汤贴,还是换汤不换药的干货文,都在不厌其烦地讲述...
    豫视西影阅读 370评论 0 6