剑指offer41——图文详解堆排序

大根堆的维护

堆数据结构可以模拟成完全二叉树(每一层节点都是从左到右添加,这层满了才能添加下一层),堆又可以用一个数组来表示如下图(实际上底层就是个数组,树只是更直观地表达而已)

这棵树有很好的性质:
1:已知父节点下标为k,那么左孩子下标为2k,右孩子为2k+1(如果左右孩子都存在的情况下)
2:已知子节点(左右孩子都一样)下标为i,那么父节点下标为(i/2),如果i/2=0那么该节点没有父节点。
在这里插入图片描述
我们先说如何通过已有的数组维护成一个大根堆(小根堆类似,只改变符号)

大根堆:所有父节点的值都大于俩孩子的值 小根堆:所有父节点的值都小于俩孩子的值

我们从非叶子节点开始维护,通过数组长度9/2可以获得最后一个非叶子节点下标为4,记为k。
然后把k这个节点的值放到数组第一个位置缓存起来。要满足大根堆的情况则k=4这个父节点必须大于俩孩子的值(如果有两个孩子的话)。通过2k,2k+1,可以获得俩孩子的下标,然后取i为子节点中值更大的节点的下标。可以发现k=4没有右孩子,2k+1>heap_len。(heap_len是数组除去第一个位置剩下元素的个数)因此i为左孩子的小标i=2k=8。发现当前节点k的值(6)小于最大的孩子i的值(8),因此把孩子的值覆盖当前的节点的值。k跳到最大的孩子的下标。如果发现k所在的位置的值大于俩孩子的值,那么就可以把第一个位置缓存的值放到k位置,结束一轮非叶子节点的维护。
在这里插入图片描述
总结:k指针指向父节点,i指针指向两子节点中值更大的节点。arr[0]位置是缓存一开始要维护的非叶子节点的值。k就是要遍历寻找一个满足条件的节点位置(父值>子值),然后把arr[0]这个数放到该位置

//从上(k位置)至下维护堆(不满足节点下沉)
void heapify(int* arr, int k, int heap_len)
{
	//先把当前要调整的节点的值放在缓存区,等找到合适的根节点的位置再把它塞进去。合适的根节点位置:arr[0] > arr[i],即根节点的值大于它最大子节点的值
	arr[0] = arr[k];
	//i是当前根节点的子节点下标
	for (int i = k * 2; i <= heap_len; i *= 2)
	{
		//i < heap_len 保证了有右孩子,如果右孩子比左孩子大则下标i更新为右孩子的下标,否则还是保持左孩子下标
		if (i < heap_len && arr[i] < arr[i + 1])
			i++;
		if (arr[0] > arr[i])
			break;

		arr[k] = arr[i];
		k = i;
	}
	arr[k] = arr[0];
}

大根堆插入元素

接下来讲大根堆插入元素,与上述不同的是,插入的操作指的是往一个维护好的大根堆里插入元素,其他位置都是维护好的。如下图,往一个大根堆里插入元素2
在这里插入图片描述
此时1和2不满足大根堆条件(父值>子值)因此要调整。这里只需要判断当前要插入元素的位置k与父节点i = k/2的值的大小关系,如果不满足条件则把他俩交换,交换完之后,k=k/2,考查父节点满不满足条件,终止条件要么都满足条件,要么没有父节点(k/2=0)

void insert(int* arr, int k)
{
	//如果当前子节点有父节点
	while (k / 2)
	{
		//父节点比当前子节点小,则把子节点换上去
		if (arr[k / 2] < arr[k])
			swap(arr[k / 2], arr[k]);
		
		k /= 2;
	}
}

在这里插入图片描述

堆排序

步骤:
1、堆顶元素就是最大值,把它扔到你想存储最终结果的地方。
2、然后在堆里将堆顶元素和最末尾的元素交换位置,并且heap_len减1,这样就不会再访问到最后那个元素了,相当于无形之间把它从堆里删除,当然如果用vector存储堆元素的话也可以pop_back()删除掉。
3、末尾元素换到堆顶肯定不满足大根堆的性质了,此时只要维护一下堆,因为其他非叶子节点没有变,只是换了堆顶的元素,所以只需要维护下标1这个位置就可以。
4、结束条件:heap_len减为0。

int main()
{

	int arr[9] = { 0,4,5,1,6,2,7,3,8 };
	int heap_len = 8;
	for (int i = heap_len / 2; i >0; i--)
	{
		heapify(arr, i, heap_len);
	}

	vector<int> vec;
	for (int i = heap_len; i > 0; i--)
	{
		vec.push_back(arr[1]);
		swap(arr[1], arr[heap_len]);
		heapify(arr, 1, --heap_len);

	}
	for (int &i :vec)
		cout << i << " ";
	return 0;
}

寻找数据流中的中位数

意思就是源源不断有数据往一个数组里面添加,要实时地更新这堆数的中位数。

核心:
1、维护一大根堆,一个小根堆。
2、大根堆里的数<小根堆里的数(大根堆堆顶元素<小根堆堆顶元素)
3、两堆元素数量不超过1
4、最终如果两堆元素数量相同,中位数等于两堆顶元素平均值;如果不同,哪堆元素数量多(多1个),那堆顶元素就是中位数。

下面给出两种实现:1、利用C++的优先队列(堆)实现 2、手撕堆(本人代码比较挫,各位见怪莫怪

优先队列实现

class MedianFinder {
private:
    // 大根堆 
    priority_queue<int, vector<int>, less<int>> maxHeap;
    // 小根堆
    priority_queue<int, vector<int>, greater<int>> minHeap;
public:
    /** initialize your data structure here. */
    MedianFinder() {
      
    }  

    void addNum(int num) {
        
        if(maxHeap.size()==0)
            maxHeap.push(num);
        else
        {
            if(num<maxHeap.top())
            {
                maxHeap.push(num);
                if(maxHeap.size()-minHeap.size()>1)
                {
                    minHeap.push(maxHeap.top());
                    maxHeap.pop();
                }
            }
            else
            {
                minHeap.push(num);
                if(minHeap.size()-maxHeap.size()>1)
                {
                    maxHeap.push(minHeap.top());
                    minHeap.pop();
                }
            }
        }

    }
    
    double findMedian() {
        if(maxHeap.size()==minHeap.size())
        {
            return (maxHeap.top()+minHeap.top())/2.0;
        }
        else
            return maxHeap.size()>minHeap.size()?maxHeap.top():minHeap.top();
    }

};

手撕堆

class MedianFinder {
private:
    int B_cont;
    int S_cont;
    vector<int> B_heap;
    vector<int> S_heap;
public:
    /** initialize your data structure here. */
    MedianFinder() {
        //大根堆维护较小的一半数
        B_heap.push_back(0);
        //大根堆里元素个数
        B_cont = 0;
        //小根堆维护较大的一半数
        S_heap.push_back(0);
        //小根堆里元素个数
        S_cont = 0;
    }
    
    //所有对堆的操作都要传引用,否则操作的只是一个副本而已!
    void B_insert(vector<int>& heap,int k)
    {
        while(k/2)
        {
            if(heap[k/2]<heap[k])
                swap(heap[k/2],heap[k]);
            k/=2;
        }
    }
    void S_insert(vector<int>& heap,int k)
    {
        while(k/2)
        {
            if(heap[k/2] > heap[k])
                swap(heap[k/2],heap[k]);
            k/=2;
        }
    }

    void B_heapify(vector<int>& heap,int k,int heap_len)
    {
        heap[0] = heap[k];
        for(int i=k*2;i<=heap_len;i*=2)
        {
            if(i<heap_len && heap[i]<heap[i+1])
                i++;
            if(heap[0]>heap[i])
                break;
            
            heap[k] = heap[i];
            k = i;
        }
        heap[k] = heap[0];
    }

    void S_heapify(vector<int>& heap,int k,int heap_len)
    {
        heap[0] = heap[k];
        for(int i=k*2;i<=heap_len;i*=2)
        {
            if(i<heap_len && heap[i]>heap[i+1])
                i++;
            if(heap[0]<heap[i])
                break;
            
            heap[k] = heap[i];
            k = i;
        }
        heap[k] = heap[0];
    }

    void addNum(int num) {
        if(B_cont==0)
        {
            B_heap.push_back(num);
            B_cont++;
        }
        else if(num>B_heap[1])
        {
            S_heap.push_back(num);
            S_cont++;
            S_insert(S_heap,S_cont);
            if((S_cont-B_cont)>1)
            {
                B_heap.push_back(S_heap[1]);
                B_cont++;
                B_insert(B_heap,B_cont);
                swap(S_heap[1],S_heap[S_cont]);
                S_cont--;
                //记得要删除!!!
                S_heap.pop_back();
                S_heapify(S_heap,1,S_cont);                
            }
        }
        else
        {
            B_heap.push_back(num);
            B_cont++;
            B_insert(B_heap,B_cont);
            if((B_cont-S_cont)>1)
            {
                S_heap.push_back(B_heap[1]);
                S_cont++;
                S_insert(S_heap,S_cont);
                // for(int &i:S_heap)
                //     cout<<i<<" ";
                cout<<endl;
                swap(B_heap[1],B_heap[B_cont]);
                B_cont--;
                //记得要删除!!!
                B_heap.pop_back();
                B_heapify(B_heap,1,B_cont); 
            }
        }

    }
    
    double findMedian() {
        //两堆数量相同返回两堆顶元素平均值
        if(B_cont==S_cont)
            return (B_heap[1]+S_heap[1])/2.0;
        //否则谁数量多,谁堆顶就是中位数(两堆数量相差最多不超过1)
        else if(S_cont>B_cont)
            return S_heap[1];
        else
            return B_heap[1];
    }

};

寻找出现频次最高的topK个单词

我这里只是简单的应用了一下,作为学习记录。很多条件没考虑进去,大佬们请忽略。。。

再次强盗一下:priority_queue与vector的sort规则完全相反

注意注意!!!!!这里priority_queue的排序和vector的sort完全相反;
如果vector的sort的话,a.second < b.second;会把第二项小的往前排,大的往后排,升序;
priority_queue刚好相反, a.second < b.second会把小的往后排,大的往前排,降序,即所谓的大根堆!堆顶元素最大。

int类型大根堆这么写:priority_queue<int, vector<int>, less<int>> p_q;
pair的话把所有int换成pair类型即可

//注意注意!!!!!这里priority_queue的排序和vector的sort完全相反
//如果vector的sort的话,a.second < b.second;会把第二项小的往前排,大的往后排,升序
//priority_queue刚好相反, a.second < b.second会把小的往后排,大的往前排,降序,即所谓的大根堆!堆顶元素最大
struct cmp {
	bool operator()(pair<string, int>& a, pair<string, int>& b)
	{
		return a.second < b.second;
	}
};

int main()
{

	vector<string> strs = { "I","really","am","a","really","cat","small","small" };
	//输出出现频次最高的前两个单词
	int top_k = 2;
	
	//key:单词 val:频次
 	unordered_map<string,int> hmap;
	for (string &str : strs)
		hmap[str]++;
	
	//优先队列传入pair时,默认是对第一项就进行,现在频次出现在第二项,因此自定义一个排序函数
	priority_queue<pair<string, int>, vector<pair<string, int>>, cmp> p_q;
	for(string &str:strs)
		//如果已经添加进优先队列的单词,频次置-1,下次在遇到就不会重复加进队列里
		if (hmap[str] != -1)
		{
			p_q.push(make_pair(str, hmap[str]));
			hmap[str] = -1;
		}
	
	while (top_k--)
	{
		cout << "key: "<< p_q.top().first <<"\t"<< "times: "<<p_q.top().second<<endl;
		p_q.pop();
	}
	

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值