数据结构6:栈、队列、堆

6.1 栈

6.1.1用两个栈实现一个队列

LeetCode No.232

题目描述:请你仅使用两个栈实现先入先出队列。队列应当支持一般队列的支持的所有操作(push、pop、peek、empty):

思路: 两个栈一个栈做队头(出元素),另一个栈做队尾(入元素)

  • 每次push只需要push到尾栈
  • pop时如果头栈为空则将尾栈全部“倒入”头栈,如果头栈不为空取出栈顶元素返回,否则返回失败(此时队列为空)。

示例代码:

type MyQueue struct {
    stack_head []int
    stack_tail []int
}


/** Initialize your data structure here. */
func Constructor() MyQueue {
    return MyQueue{
        stack_head: make([]int, 0),
        stack_tail: make([]int, 0),
    }
}


/** Push element x to the back of queue. */
func (this *MyQueue) Push(x int)  {
    this.stack_tail = append(this.stack_tail, x)
}

func(this *MyQueue) tail2head()  {
    for i := len(this.stack_tail) - 1; i >= 0; i-- {
        this.stack_head = append(this.stack_head, this.stack_tail[i])
        this.stack_tail = this.stack_tail[:len(this.stack_tail) - 1]
    }
}

/** Removes the element from in front of queue and returns that element. */
func (this *MyQueue) Pop() int {
    if len(this.stack_head) == 0 {
        this.tail2head()
    }
    if len(this.stack_head) > 0 {
        r := this.stack_head[len(this.stack_head) - 1]
        this.stack_head = this.stack_head[:len(this.stack_head) - 1]
        return r
    }
    return -1
}


/** Get the front element. */
func (this *MyQueue) Peek() int {
    if len(this.stack_head) == 0 {
        this.tail2head()
    }
    if len(this.stack_head) > 0 {
        return this.stack_head[len(this.stack_head) - 1]
    }
    return -1
}


/** Returns whether the queue is empty. */
func (this *MyQueue) Empty() bool {
    return len(this.stack_head) == 0 && len(this.stack_tail) == 0
}

6.1.2 逆波兰表达式求值

LeetCode No.150

问题描述:根据 逆波兰表示法,求表达式的值。
有效的运算符包括 +, -, *, / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
输入: ["2", "1", "+", "3", "*"]
输出: 9
解释: 该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9

思路:借助一个栈,如果是数值就入栈,如果是运算符就从栈顶取出两个元素计算,然后将结果入栈,最后栈中只剩一个元素就是结果。

示例代码:

func operate(x, y int, op string) int {
    switch op {
    case "+": return x + y
    case "-": return x - y
    case "*": return x * y
    case "/": return x / y
    default:
        return 0
    }
}

func evalRPN(tokens []string) int {
    stack := []int{}
    for i := 0; i < len(tokens); i++ {
        switch tokens[i] {
        case "+", "-", "*", "/":
            cur := operate(stack[len(stack) - 2], stack[len(stack) - 1], tokens[i])
            stack = stack[:len(stack) - 1]
            stack[len(stack) - 1] = cur
        default:
            num, _ := strconv.Atoi(tokens[i])
            stack = append(stack, num)
        }
    }
    return stack[0]
}

6.1.3 中缀表达式生成逆波兰表达式

  • 借助一个符号栈和结果队列,具体过程见代码注释

示例代码:

func is_operation(b byte) bool {
    return b == '+' || b == '-' || b == '*' || b == '/'
}

func compare_priority(a, b byte) int {
    if (a == '+' || a == '-') && (b == '*' || b == '/') {
        return -1
    } else if (b == '+' || b == '-') && (a == '*' || a == '/') {
        return 1
    } else {
        return 0
    }
}

func toRPN(s string) []string {
    // 运算符栈
    ops_stack := []byte{}
    // 结果队列
    res_queue := []string{}
    n := len(s)
    for i := 0; i < n; i++ {
        if s[i] == '(' {
            // 遇到左括号直接入栈
            ops_stack = append(ops_stack, s[i])
        } else if s[i] == ')' {
            // 遇到右括号,则将栈中的运算符依次弹出加入到结果队列,直到碰到左括号,然后将这对括号丢弃
            for ops_stack[len(ops_stack) - 1] != '(' {
                res_queue = append(res_queue, string(ops_stack[len(ops_stack) - 1]))
                ops_stack = ops_stack[:len(ops_stack) - 1]
            }
            ops_stack = ops_stack[:len(ops_stack) - 1]
        } else if is_operation(s[i]) {
            // 遇到运算符,当前运算符的优先级小于等于栈顶运算符的优先级,则依次将栈顶弹出加入到结果队列
            for len(ops_stack) > 0 && is_operation(ops_stack[len(ops_stack) - 1]) &&
                compare_priority(s[i], ops_stack[len(ops_stack) - 1]) <= 0 {
                res_queue = append(res_queue, string(ops_stack[len(ops_stack) - 1]))
                ops_stack = ops_stack[:len(ops_stack) - 1]
            }
            ops_stack = append(ops_stack, s[i])
        } else if s[i] == ' ' {
            // 跳过空字符
            continue
        } else {
            // 遇到数字加入到结果队列
            num := 0
            for ; i < n && s[i] >= '0' && s[i] <= '9'; i++ {
                num = num * 10 + int(s[i] - '0')
            }
            i--
            res_queue = append(res_queue, strconv.Itoa(num))
        }
    }
    // 运算符栈中剩余的元素弹出添加到结果队列
    for len(ops_stack) > 0 {
        res_queue = append(res_queue, string(ops_stack[len(ops_stack) - 1]))
        ops_stack = ops_stack[:len(ops_stack) - 1]
    }
    return res_queue
}

6.2 堆

定义:最大堆的堆顶为最大元素,最小堆同理

6.2.1 Golang实现堆类型

因为go本身没有实现堆类型,只提供了接口,使用时必须实现堆接口才能使用对应的堆方法,所以自己用golang的container/heap接口实现一个通用的堆类型,创建堆需要一个比较函数作为参数
实现代码:

// 比较函数类型
type Comparator func(a, b interface{}) bool
// 堆元素类型
type Elements struct {
    es   []interface{}
    cmp Comparator
}
// 堆类型
type Heap struct {
    elements *Elements
}

// 创建堆
func NewHeap(cmp Comparator) *Heap {
    return &Heap{
        elements: &Elements{
            es: make([]interface{}, 0),
            cmp: cmp,
        },
    }
}

// 堆元素实现了container/heap接口
func (e Elements) Len() int { return len(e.es) }
func (e Elements) Less(i, j int) bool { return e.cmp(e.es[i], e.es[j]) }
func (e Elements) Swap(i, j int)      { e.es[i], e.es[j] = e.es[j], e.es[i] }

func (e *Elements) Push(item interface{}) { e.es = append(e.es, item) }
func (e *Elements) Pop() interface{} {
    length := len(e.es)
    if length == 0 {
        return nil
    }
    top := e.es[length - 1]
    e.es = e.es[:length - 1]
    return top
}

// 入堆
func (h *Heap) Push(i interface{}) {
    heap.Push(h.elements, i)
}

// 堆顶元素出堆
func (h *Heap) Pop() interface{} {
    return heap.Pop(h.elements)
}

// 查看堆顶元素
func (h Heap) Top() interface{} {
    if len(h.elements.es) == 0 {
        return nil
    }
    return h.elements.es[0]
}

// 获取堆大小
func (h Heap) Len() int  {
    return h.elements.Len()
}

func CompareInt(a, b interface{}) bool {
    if a.(int) > b.(int) {
        return true
    }
    return false
}

6.2.2 数组中的第K个最大元素

LeetCode No.215

问题描述:在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5

思路:使用最小堆,首先将前k个元素加入堆作为目前的最大的k个元素,然后遍历剩下的n-k个元素,如果堆顶元素比当前元素小,取出堆顶,将当前元素加入堆。最后堆顶的元素即为第k大的元素。
时间复杂度:O(n*log(n))
空间复杂度:O(k)

示例代码:

func findKthLargest(nums []int, k int) int {
    heap1:= NewHeap(CompareInt)
    // 前k个元素建立大小为k的小顶堆
    for i := 0; i < k; i++ {
        heap1.Push(nums[i])
    }
    // 遍历剩余的元素更新堆
    for i := k; i < len(nums); i++ {
        top := heap1.Top().(int)
        if top > nums[i] {
            heap1.Pop()
            heap1.Push(nums[i])
        }
    }
    return heap1.Top().(int)
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容