Merge k Sorted Lists:
Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.
题意:
把k个已经排好序的队列重新排成一个完整的队列
题解:
这一道题的含义就跟归并排序的合并步骤一样,也就是有序队列的合并。
- 一开始想直接进行k的队列的每一次寻找最小值然后进行合并,但是看到了这一道题的难度是hard,所以上面的算法的效率是非常低的:设元素的数目为N个,所以大概的比较次数为k*N,也就是每个元素的都需要进行k次比较,时间消耗一定会比较巨大,所以这种方法是行不通的。
- 这一道题是在搜索分治算法的时候找到的,但是觉得这一道题大概跟分治的关系也就是前面所提到的?所以可能没有分治思维的方法。
- 然后就开始考虑进行排序算法,排序算法最快的速度也就是nlogn,效率还不如寻找最大值。
- 在思考排序算法的时候刚好想到了堆排序,想起了之前数据结构老师说的堆排序是很重要的,然后想到了建堆的步骤,以及插入和删除,而这个合并有序序列只需要提供最小值也就可以了,所以用小顶堆是一个不错的算法,因为一开始建堆需要的比较次数为klogk,然后每一次删除以及插入所需要的比较次数是logk,所以总的复杂度是Nlogk,比起k*N算是一个不错的进步了,特别是k很多的时候体现会更好。
所以解题的代码如下:
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
//获取向量的大小
length = lists.size();
//利用迭代器删除向量中的空元素,从测例中发现的[[]]等样例
vector<ListNode*>::iterator it = lists.begin();
for(;it != lists.end();)
{
if(*it == nullptr)
{
it = lists.erase(it);
length--;
}
else
//迭代器指向下一个元素位置
++it;
}
//不存在元素时返回空值
if(length == 0){
return nullptr;
}
//只有一个非空有序序列时返回该序列的指针
if(length == 1){
return lists[0];
}
//建堆
for (int i = length / 2 - 1; i >= 0; i--)
MinHeapDown(lists, i, length);
//声明结果头节点
ListNode * a = new ListNode(lists[0]->val);
//声明辅助节点
ListNode * temp = a;
while(length != 1){
MinHeapDelete(lists);
if(delete_->next != nullptr){
MinHeapAdd(lists, length, delete_->next);
}
temp->next = new ListNode(lists[0]->val);
temp = temp->next;
}
//用于只剩一个或者两个有序序列时的元素的补充(因为有可能另一个被删除了,但是还剩余元素)
if(length == 1){
lists[0] = lists[0]->next;
while(delete_->next != NULL && lists[0] != NULL){
if(delete_->next->val > lists[0]->val){
temp->next = new ListNode(lists[0]->val);
temp = temp->next;
lists[0] = lists[0]->next;
}
else{
temp->next = new ListNode(delete_->next->val);
temp = temp->next;
delete_ = delete_->next;
}
}
while(lists[0] != NULL){
temp->next = new ListNode(lists[0]->val);
temp = temp->next;
lists[0] = lists[0]->next;
}
while(delete_->next != NULL){
temp->next = new ListNode(delete_->next->val);
temp = temp->next;
delete_ = delete_->next;
}
}
return a;
};
//添加新元素
void MinHeapAdd(vector<ListNode*>& lists, int n, ListNode * new_){
length++;
lists[length - 1] = new_;
MinHeapUp(lists, length - 1);
}
//删除元素
void MinHeapDelete(vector<ListNode*>& lists){
length--;
swap(lists[0], lists[length]);
delete_ = lists[length];
MinHeapDown(lists, 0, length);
}
//添加新的元素(添加到尾部后再进行重新排序)
void MinHeapUp(vector<ListNode*>& lists, int n){
int j;
ListNode* temp = lists[n];
j = (n - 1) / 2;
while(j >= 0 && n != 0){
if (lists[j]->val <= temp->val) break;
lists[n] = lists[j];
n = j;
j = (n - 1) / 2;
}
lists[n] = temp;
}
//堆排序的下沉算法(用于堆的建立以及元素的删除)
void MinHeapDown(vector<ListNode*>& lists, int i, int n){
int j;
ListNode* temp = lists[i];
j = 2 * i + 1;
while(j < n){
if(j + 1 < n && lists[j + 1]->val < lists[j]->val) j++;
if(lists[j]->val > temp->val) break;
lists[i] = lists[j];
i = j;
j = 2 * i + 1;
}
lists[i] = temp;
}
//用于本地的代码测试(输出结果)
void print(ListNode *result){
ListNode * temp = result;
while(temp != NULL && temp->next!=NULL){
cout << temp->val << " -> ";
temp = temp->next;
}
if(temp == NULL) return ;
cout << temp->val << endl;
}
private:
//用于存储向量的大小
int length;
//用于存储最近的被删除的节点(为了记录需要重新加入的元素的上一个结点)
ListNode * delete_;
};
在写这一部分代码的时候还是有点手生的,然后参考了一下https://blog.csdn.net/hrn1216/article/details/51465270(最小堆 构建、插入、删除的过程图解)这一篇博客,因为有图解看起来会比较好懂些。
后记:
在disscuss中查找了一下min heap C++之后看到了一个
Beats 100% method, using min heap by make_heap, pop_heap, push_heap
的答案,看了一下原来STL还自带提供了min heap的有关的函数方法,而且还优化的这么好,所以就学习了一波。
- make_heap(实际上是用 priority_queue 来实现的)
default (1) template <class RandomAccessIterator> void make_heap (RandomAccessIterator first, RandomAccessIterator last);
custom (2) template <class RandomAccessIterator, class Compare> void make_heap (RandomAccessIterator first, RandomAccessIterator last, Compare comp );
Rearranges the elements in the range[first,last)
in such a way that they form a heap.
A heap is a way to organize the elements of a range that allows for fast retrieval of the element with the highest value at any moment (with pop_heap), even repeatedly, while allowing for fast insertion of new elements (with push_heap).
The element with the highest value is always pointed by first. The order of the other elements depends on the particular implementation, but it is consistent throughout all heap-related functions of this header.
- push_heap 以及 pop_heap 详见 http://www.cplusplus.com/reference/algorithm/