优先队列

优先队列

优先队列(priority queue):

可以在O(1)时间获得最大值,在O(log n)时间内取出或者插入最大值。常用堆(heap)来实现。

1.实现

实现队列和栈与实现优先队列的不同在于性能的要求。队列和栈实现在常数时间完成所有操作。使用基础结构来实现优先队列,在最坏情况下插入元素删除最大元素需要用线性时间来完成。基于堆来实现优先队列可以保证两个操作在logN的时间完成。

  • 一个完全二叉树,每个节点的值总是大于等于子节点的值。通常使用一个数组来建立一个树。堆的两个核心操作是上浮和下沉。如果一个节点比父节点大,那么需要交换两个节点,一直不断比较和交换:这个操作为上浮。下沉同理,如果有两个子节点比父节点大,则选择较大的子节点。节点i,父节点i/2,子节点是2i与2i+1。

1.1 C++代码

vector<int> heap;

//获得最大值
void top(){
    return heap[0];
}

//插入任意值:把新的数字放在最后一位,然后上浮
void push(int k){
    heap.push_back(k);
    swim(heap.size()-1);
}

//删除最大值:把最后一个数字挪到开头然后下沉
void pop(){
    heap[0] = heap.back();
    heap.pop_back();
    sink(0);
}

//上浮
void swim(int k){
    while(k>1 && heap[k/2] < heap[k]){
        swap(heap[k/2] , heap[k]);
        k = k/2;
    }
}

//下沉
void sink(int k){
    while(2*k <=N){
        int j = 2*k;
        if(j < N && heap[j]<heap[j+1])
            j++;
        if(!heap[k]<heap[j])
            break;
        swap(heap[k], heap[j]);
        k = j;
    }
}

1.2 Java代码


pubilc class MaxPQ<Key extends Comparable<Key>>{
    private Key[] pq;//基于堆的完全二叉树
    private int N = 0;//存储与pq[1..N]中,pq[0]没有使用
    
    public MaxPQ(int maxN){
    	pq = (Key[]) new Comparable[maxN+1];
    }
    public boolean isEmpty(){
    	return N == 0;
    }
    public int size(){
        return N;
    }
    public void insert(Key v){
    	pq[++N] = v;
        swim(N);
    }
    public Key delMax(){
        Key max = pq[1];//从根节点得到最大元素
        exch(1, N--);//将其和最后一个节点交换
        pd[N+1] = null;//防止对象游离
        sink(1);//恢复堆的有序性
        return max;
    }
    //辅助方法
    private boolean less(int i, int j){
        return pq[i].compareTo(pq[j])<0;
    }
    //交换
    private void exch(int i, int j){
        Key t = pq[i];
        pq[i] = pq[j];
        pq[j] = t;
    }
    //上浮
    private void swim(int k){
        while(k>1 && less(k/2,k)){
            exch(k/2, k);
            k = k/2;
        }
    }
    //下沉
    private void sink(int k){
        while(2*k<=N){
            int j = 2*k;
            if(j<N && less(j, j+1))//选择较大的子节点来比较
                j++;
            if(!less(k,j))//如果大于子节点直接break
                break;
            exch(k, j);
            k = j;
        }
    }
    
    
}

2.题目

2.1合并k个增序的链表(LeetCode 23 Hard)

除了利用归并排序进行两两合并,还可以使用优先队列(最小堆)来快速解决。

最小堆思路:用优先队列存储k个链表的首元素,然后每次提取最小的元素加入最终的结果链表dummy。然后把取出元素的下一个元素再加入到堆中,以此类推。直到所有链表被提取结束。

C++版本

//比较函数来维持递减的关系
struct Comp{
    bool operator()(ListNode* l1, ListNode* l2){
        return l1->val > l2->val;
    }
};

//
ListNode* mergeKLists(vector<ListNode*>& lists){
    if(lists.empty())
        return nullptr;
    priority_queue<ListNode*, vector<ListNode*>, Comp> q;
    for(ListNode* list: lists){
        if(list)
            q.push(list);
    }
    ListNode* dummy = new ListNode(0);
    ListNode* *cur = dummy;
    while(!q.empty()){
    	cur->next = q.top();
        q.pop();
        cur=cur->next;
        if(cur->next)
            q.push(cur->next);
    }
    return dummy->next;
}

java版本:

public ListNode mergeKLists(ListNode[] lists) {
        if (lists == null || lists.length == 0) return null;
        PriorityQueue<ListNode> queue = new PriorityQueue<>(lists.length, new Comparator<ListNode>() {
            @Override
            public int compare(ListNode o1, ListNode o2) {
                if (o1.val < o2.val) return -1;
                else if (o1.val == o2.val) return 0;
                else return 1;
            }
        });
        ListNode dummy = new ListNode(0);
        ListNode p = dummy;
        for (ListNode node : lists) {
            if (node != null) queue.add(node);
        }
        while (!queue.isEmpty()) {
            p.next = queue.poll();
            p = p.next;
            if (p.next != null) queue.add(p.next);
        }
        return dummy.next;
    }

2.2 丑数

在这里插入图片描述

1)丑数I:依次整除,最后判断是否为1。

2)丑数II:从下往上寻找丑数。

int nthUglyNumber(int n) {
        vector<int> nums;
        nums.push_back(1);
        int i2 = 0, i3 = 0, i5 = 0;
        for(int i= 1; i < n; i++){//注意这里从1开始
            int ugly = min(nums[i2]*2, min(nums[i3]*3,nums[i5]*5));//从小到大算出丑数
            nums.push_back(ugly);
            //依次*2,*3,*5,
            if(nums[i]==nums[i2]*2) ++i2;
            if(nums[i]==nums[i3]*3) ++i3;
            if(nums[i]==nums[i5]*5) ++i5;
        }
        return nums[n-1];
    }

3)丑数III:此题的丑数定义与263. 丑数264. 丑数 II并不相同。是困难的 878. 第N个神奇数字 的升级版。寻找小于等于x的a的倍数、b的倍数、c的倍数组成的集合分别称为A、B、C。由容斥原理

在这里插入图片描述

可得:
在这里插入图片描述

则小于等于x的丑数个数为

在这里插入图片描述

对于f(x)进行二分查找即可。

int nthUglyNumber(int n, int a, int b, int c){
    long ab = lcm(a, b);      // a,b的最大公倍数
        long ac = lcm(a, c);      // a,c的最大公倍数
        long bc = lcm(b, c);      // b,c的最大公倍数
        long t = lcm(ab, c);      // a,b,c三个数的最大公倍数
        long floor = min(a, min(b, c));
        long ceil = 2e9;
        while(floor < ceil){
            long mid = (ceil - floor) / 2 + floor;
            // num为[1...mid]中的丑数个数(容斥原理)
            long num = mid / a + mid / b + mid / c - mid / ab - mid / ac - mid / bc + mid / t;
            if(num >= n){
                ceil = mid;
            }
            else{
                floor = mid +1;
            }
        }
        return floor;
}
//最大公因数
long gcd(long x, long y){
    return !y?x:gcd(y, x%y);
}

//最小公倍数
long lcm(long x, long y){
    return x*y / gcd(x,y); 
}





4)超级丑数:

超级丑数II题目的扩展版本

依然维持一个数组nums

维持一个primes对应的序号

class Solution {
public:
    int nthSuperUglyNumber(int n, vector<int>& primes) {
        vector<int> nums;
        nums.push_back(1);

        int np = primes.size();
        // primes的对应nums的序号
        vector<int> plist(np,0);

        for (int i = 1; i < n; ++i)
        {
            int currMin = INT_MAX;
            for (int j = 0; j < np; ++j)
            {
                currMin = min(currMin, nums[plist[j]]*primes[j]);
            }
            nums.push_back(currMin);

            for (int j = 0; j < np; ++j)
            {
                if (currMin == nums[plist[j]]*primes[j])
                {
                    ++plist[j]; 
                }
            }
        }

        return nums[n-1];
    }
};

2.3 taxicab number

在这里插入图片描述

想象一个二维矩阵m [i] [j] = i ^ 3 + j ^ 3。我们不必在内存中创建此矩阵。行的项目按升序排列,列的项目也按升序排列。我们可以使用minHeap(minPQ)首先存储对角线。然后执行以下步骤,直到minHeap为空:

  1. 从minHeap获取当前的min(minCur),将其与preMin进行比较,如果它们相等,我们将找到一对和(a ^ 3 + b ^ 3 = c ^ 3 + d ^ 3)。
  2. 将项目放置在minCur右侧的minHeap矩阵中。

该算法起作用是因为它保证矩阵中的所有项目都按顺序添加并从minHeap中取出。

class Taxicab implements Comparable<Taxicab>{
        int n1;
        int n2;
        int cube;

        Taxicab(int n1, int n2) {
            this.n1 = n1;
            this.n2 = n2;
            this.cube = n1 * n1 * n1 + n2 * n2 * n2;
        }

        @Override
        public int compareTo(Taxicab that) {
            if (that.cube > this.cube) return -1;
            if (that.cube < this.cube) return 1;
            return 0;
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof Taxicab) {
                if (((Taxicab)o).compareTo(this) == 0)
                return true;
            }
            return false;
        }

        @Override
        public String toString() {
            return "number: " + cube + " (" + n1 + ", " + n2 + ")";
        }
    }

    public void findTaxinumber(int N) {
        MinPQ<Taxicab> candidates = new MinPQ<Taxicab>();

        for (int i = 1; i <= N; i++) {
            for (int j = i + 1; j <= N; j++) {
                Taxicab t = new Taxicab(i, j);
                if (candidates.size() < N) {
                    candidates.insert(t);
                }
                else {
                    Queue<Taxicab> temp = new Queue<Taxicab>();
                    Taxicab min = candidates.delMin();
                    while (candidates.min().equals(min)) {
                        temp.enqueue(candidates.delMin());
                    }
                    if (!t.equals(min)) {
                        candidates.insert(t);
                    }
                    else {
                        temp.enqueue(t);
                    }
                    if (!temp.isEmpty()) {
                        for (Taxicab taxi: temp) {
                            System.out.println(taxi);
                        }
                        System.out.println(min);
                    }
                }
            }
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值