堆堆排序加强堆和堆有关的题一网打尽

news2025/1/24 4:59:59

堆结构、堆排序

堆结构的实现

  1. 堆结构就是用数组实现的完全二叉树结构

2)完全二叉树中如果每颗子树的最大值都在顶部就是大根堆

3)完全二叉树中如果每颗子树的最小值都在顶部就是小根堆

4)堆结构的向上调整和向下调整算法

向上调整

image-20220312191242033

向下调整

5)堆结构某个元素的增大和减少

6)优先级队列结构就是堆结构


空树是完全二叉树,完全二叉树的性质:

如果父亲下标为i : 左孩子:2*i+1 右孩子: 2*i+2

如果孩子下标为i: 父亲的下标:(i-1)/2

#pragma once
#include<iostream>
#include<algorithm>
using namespace std;
template<class T>
//大堆结构
class Heap
{
public:
	//构造函数
	Heap()
		:_a(nullptr)
		,_size(0)
		,_capacity(0)
	{

	}
	int size()
	{
		return _size;
	}
	int empty()
	{
		return _size == 0;
	}
	//返回堆顶元素
	T& top()
	{
		return _a[0];
	}
	//析构函数
	~Heap()
	{
		free(_a);
		_a = nullptr;
		_size = _capacity = 0;
	}
	//打印堆中的元素
	void Print()
	{
		for (int i = 0; i < _size; i++)
		{
			cout << _a[i] << " ";
		}
	}
	//向下调整算法
	//从parent位置向下调整
	void AdjustDown(int parent)
	{
		//何时调整结束:调整到叶子节点 或者 父亲节点的值大于(小于)孩子节点
		int child = parent * 2 + 1;//默认较大的是左孩子
		while (child < _size)
		{
			//选出较大的孩子
			//防止没有右孩子导致越界
			if (child + 1 < _size && _a[child] < _a[child + 1])
			{
				++child;//右孩子更大
			}
			//判断是否需要交换
			if (_a[child] < _a[parent])
			{
				swap(_a[child], _a[parent]);//父亲和孩子交换
				parent = child;//迭代往下调整, 父亲到达孩子的位置
				child = parent * 2 + 1;//重新计算孩子的下标
			}
			else
			{
				break;
			}
		}
	}

	//向上调整算法
	//从child位置向上调整
	void AdjustUp(int child)
	{
		//何时调整结束:调整到根节点 或者 孩子节点的值大于(小于)父亲节点
		//当child = 0,可以跳出循环
		//或者孩子节点的值<父亲节点的值可以跳出循环
		while (_a[child] > _a[(child - 1) / 2])
		{
			swap(_a[child], _a[(child - 1) / 2]);
			child = (child - 1) / 2;//孩子调整到父亲位置
		}
	}
	//弹出堆顶元素
	void HeadPop()
	{
		//堆顶元素和最后一个元素交换,然后堆中元素个数--,再从堆顶向下调整
		swap(_a[0], _a[_size]);
		_size--;
		AdjustDown(0);//从0位置开始向下调整
	}
	//检查容量
	void CheakCapacity()
	{
		//插入到_size位置,要检查容量
		if (_size == _capacity)
		{
			//扩容
			int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
			T* tmp = new T[newcapacity];//开辟newcapacity个空间,元素类型是T
			//把原来的数据插入到新空间
			for (int i = 0; i < _size; i++)
			{
				tmp[i] = _a[i];
			}
			delete[] _a;//释放旧空间
			_a = tmp;//_a指向新空间
			_capacity = newcapacity;//更新容量
		}
	}
	//插入元素到_size位置
	void HeapInsert(T val)
	{
		CheakCapacity();
		_a[_size] = val;//插入到_size位置
		_size++;//堆中元素个数++
		AdjustUp(_size-1);//从_size-1位置向下调整
	}
private:
	T* _a;
	int _size;//堆中元素个数
	int _capacity;//容量
};

堆排序的实现

1.先让整个数组都变成大根堆结构,建立堆的过程:

1)从上到下的方法: 时间复杂度:O(N*logN)

2)从下到上的方法:时间复杂度:O(N)

2.把堆顶数据和堆尾数据交换,然后减少堆的大小,再去调堆,一直周而复始,时间复杂度:O(N*logN)

3.堆的大小减小成0之后,排序完成

image-20220312191454358

//向上调整
void heapInsert(int* a, int index)
{
	while (a[index] > a[(index - 1) / 2]) {
		swap(a[index], a[(index - 1) / 2]);
		index = (index - 1) / 2;
	}
}
//向下调整
void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1;//默认是左孩子
	while (child < n)
	{
		//如果有右孩子且右孩子的值>左孩子则child变成右孩子,选出较大的孩子
		if (child + 1 < n && a[child + 1] > a[child])
		{
			child += 1;
		}
		//如果大的孩子的值比父亲大,则交换,迭代继续往下调整
		//parent的下标比child下标的值小,往下调整,parent下标变大,即要把child的下标赋给parent,然后再迭代找孩子比较
		if (a[child] > a[parent])
		{
			swap(a[child], a[parent]);
			//迭代
			parent = child;//切记这里是把孩子的下标给父亲,不要和向上调整弄混
			child = parent * 2 + 1;
		}
		//大的孩子的值比父亲的值小,不用调整了
		else
		{
			break;
		}
	}
}
void heapSort(int* a, int n)
{
	if (a == nullptr || n < 2)
	{
		return;
	}
	//1.建堆
	//方法1:向上调整建堆:O(N*logN)
	/*for (int i = 0; i < n; i++)
	{
		heapInsert(a, i);
	}*/
    
	//方法2:向下调整建堆
	//从	数组的最后一个元素开始调整
	for (int i = n - 1; i >= 0; i--)
	{
		AdjustDown(a, n, i);//从i位置开始向下调整,共有n个元素
	}

	int heapsize = n;//堆中元素个数
	//堆顶和堆尾元素交换,然后堆中元素减少一个
	swap(a[0], a[--heapsize]);

	//每个数都需要调堆,直到堆元素个数为0
	while (heapsize > 0)
	{
		AdjustDown(a, heapsize,0);//O(N*logN)  //从0位置开始向下调整,共heapsize个元素
		swap(a[0], a[--heapsize]);//堆顶和堆尾元素交换,然后堆中元素减少一个
	}
}

题目:数组每个元素移动k步排好序

已知一个几乎有序的数组。几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离一定不超过k, k相对于数组长度来说是比较小的。请选择一个合适的排序策略,对这个数组进行排序。


首先:整个数组的最小值肯定是在 位置0~位置k这个范围里的,这K+1个数组成一个小根堆,这个小根堆的堆顶肯定是整个数组的最小值,把堆顶元素弹出放在数组的 位置0 上

然后再加入一个数,第二小的数肯定在原来的位置1~位置k+1的范围上…

image-20220312191522221


加一个数弹一个数,最后没得加了,就依次弹出堆顶数据

一共弹出N个数,小根堆只有K个数,调堆高度为:logK

所以时间复杂度: O(N*logK) 如果K比较小 时间复杂度O(N)

空间复杂度:K+1 堆中元素不超过k+1个

#include<vector>
#include<queue>
#include<functional>
void sortedArrDistanceLessK(vector<int> v, int k)
{
	if (k == 0)//说明已经有序
	{
		return;
	}
	//准备一个小根堆
	priority_queue<int, vector<int>, greater<int>>  q;
	//创建k个数的小根堆
	int index = 0; //0->k-1
	//注意:有可能数组元素不够k个
	int n = v.size()  > k ? k  : v.size() ;
	for (index = 0; index < n; index++)
	{
		q.push(v[index]);//把前k个数进堆
	}
	int i = 0;
	//依次从原数组加入数据到堆中 和 弹出数据放到原数组
	for (; index < v.size(); index++)
	{
		q.push(v[index]);//加入数据
		//弹出数据放到原数组
		v[i++] = q.top();
		q.pop();
	}

	//最后没得加了,就依次弹出堆中数据,放到原数组
	while (!q.empty())
	{
		v[i++] = q.top();
		q.pop();
	}
}

和堆有关的面试题、加强堆结构

线段最大重合问题

题目:

给定很多线段,每个线段都有两个数[start, end],
表示线段开始位置和结束位置,左右都是闭区间
规定:
1)线段的开始和结束位置一定都是整数值
2)线段重合区域的长度必须>=1
返回线段最多重合区域中,包含了几条线段

方法1:枚举

方法1:枚举

image-20220320160501161


image-20220320160512205


参数说明:

image-20220320160524652


线段重合__牛客网 (nowcoder.com)

求的是某个区域包含多少条线段!

image-20220320185905636

#include<iostream>
#include<vector>
using namespace std;
//线段区间结构体
struct Line
{
    int start;//线段起始位置
    int end;//线段末尾位置
};
int main()
{
    int n;
    cin >> n;
    vector<Line> v(n); //容器存的是线段区间
    //共有n条线段
    for (int i = 0; i < n; i++)
    {
        cin >> v[i].start >> v[i].end;
    }
    //遍历所有线段,找到起始位置的最小值和末尾位置的最大值
    int max = v[0].end;
    int min = v[0].start;
    for (int i = 0; i < n; i++)
    {
        if (v[i].end > max)
        {
            max = v[i].end;
        }
        if (v[i].start < min)
        {
            min = v[i].start;
        }
    }
    int maxSum = 0;
    //枚举,以每个值+小数开始枚举
    for (double p = min + 0.5; p < max + 0.5; p+=1)
    {
        int sum = 0;
        //考察每一个值被多少条线段包含
        for (int i = 0; i < n; i++)
        {
            //被包含:即区间的起始位置<p,区间的末尾位置>p
            if (v[i].start<p && v[i].end > p)
            {
                sum++;
            }
        }
        //更新每条线段被包含的最大值
        maxSum = maxSum > sum ? maxSum:sum;
    }
    cout << maxSum << endl;
}

题目2:求的是端点最多被几条线段包含!!

image-20220320185547318

#include<iostream>
#include<vector>
using namespace std;
//线段区间结构体
struct Line
{
    int start;//线段起始位置
    int end;//线段末尾位置
};
int main()
{
    int n;
    cin >> n;
    vector<Line> v(n); //容器存的是线段区间
    //共有n条线段
    for (int i = 0; i < n; i++)
    {
        cin >> v[i].start >> v[i].end;
    }
    //遍历所有线段,找到起始位置的最小值和末尾位置的最大值
    int max = v[0].end;
    int min = v[0].start;
    for (int i = 0; i < n; i++)
    {
        if (v[i].end > max)
        {
            max = v[i].end;
        }
        if (v[i].start < min)
        {
            min = v[i].start;
        }
    }
    int maxSum = 0;
    //枚举,看每一个端点最多重合多少条线段
    for (int p = min ; p < max; p+=1)
    {
        int sum = 0;
        //考察每一个端点被多少条线段包含
        for (int i = 0; i < n; i++)
        {
            //被包含(含有该值在里面):即区间的起始位置<=p,区间的末尾位置>=p
            if (v[i].start<=p && v[i].end >= p)
            {
                sum++;
            }
        }
        //更新每个位置被包含的最大值
        maxSum = maxSum > sum ? maxSum:sum;
    }
    cout << maxSum << endl;
}

方法2:

image-20220320160810484


方法2:使用堆

题目:

线段重合__牛客网 (nowcoder.com)

image-20220320185905636

image-20220320161028700

上图解析为例题1的解析

#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
//线段区间结构体
struct Line
{
    int start;//线段起始位置
    int end;//线段末尾位置
};
//注意要传引用
bool cmp(Line& x, Line& y)
{
    return x.start < y.start;
}
int main()
{
    //将所有线段放入vector中,并根据线段的起始位置排升序
    //遍历所有线段,将vector中线段的结束位置放入小根堆q中
    //如果小根堆中的有线段的结束位置比当前线段起始位置<=则弹出堆
    int n;
    cin >> n;
    vector<Line> v(n); //容器存的是线段区间
    //共有n条线段
    for (int i = 0; i < n; i++)
    {
        cin >> v[i].start >> v[i].end;
    }
    //1.根据线段的起始位置进行排序
    sort(v.begin(), v.end(), cmp);
    //2.准备一个小根堆,用于存放区间端点
    priority_queue<int, vector<int>, greater<int>> q;
    int maxSum = 0;
    //3.依次考察每一条线段,共n条
    for (int i = 0; i < n; i++)
    {
        //把小根堆<=开始位置的值弹出,然后把此时线段的结束位置放入到小根堆中
        while (!q.empty() && q.top() <=v[i].start)
        {
            q.pop();
        }
        //把此时区间的结束位置放到小根堆中
        q.push(v[i].end);
        //看此时小根堆中有多少个元素
        int sum = q.size();
        //更新最大值
        maxSum = maxSum > sum?maxSum:sum;
    }
    cout << maxSum << endl;
}

例题2:

线段重合__牛客网 (nowcoder.com)

image-20220320185547318

#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
//线段区间结构体
struct Line
{
    int start;//线段起始位置
    int end;//线段末尾位置
};
//注意要传引用
bool cmp(Line& x, Line& y)
{
    return x.start < y.start;
}
int main()
{
    //将所有线段放入vector中,并根据线段的起始位置排升序
    //遍历所有线段,将vector中线段的结束位置放入小根堆q中
    //如果小根堆中的有线段的结束位置比当前线段起始位置小则弹出堆
    int n;
    cin >> n;
    vector<Line> v(n); //容器存的是线段区间
    //共有n条线段
    for (int i = 0; i < n; i++)
    {
        cin >> v[i].start >> v[i].end;
    }
    //1.根据线段的起始位置进行排序
    sort(v.begin(), v.end(), cmp);
    //2.准备一个小根堆,用于存放区间端点
    priority_queue<int, vector<int>, greater<int>> q;
    int maxSum = 0;
    //3.依次考察每一条线段,共n条
    for (int i = 0; i < n; i++)
    {
        //把小根堆<开始位置的值弹出,然后把此时线段的结束位置放入到小根堆中
        while (!q.empty() && q.top() <v[i].start)
        {
            q.pop();
        }
        //把此时区间的结束位置放到小根堆中
        q.push(v[i].end);
        //看此时小根堆中有多少个元素
        int sum = q.size();
        //更新最大值
        maxSum = maxSum > sum?maxSum:sum;
    }
    cout << maxSum << endl;
}

上述例题1和例题2有所不同的原因及代码区别:

可以认为例题1中**:端点重合的线段并不算包含**,必须是经过里面的区域 如[1,2] 和[2,3] 端点2重合,但是这两条线段并不包含

而例题2中:端点重合的线段算包含

image-20220320212055788


image-20220320212113770


加强堆的实现

堆的不足之处:

  • 1.堆不能做到修改堆中元素的值
  • 2.只能删掉堆顶元素,不能删除堆中其他元素,因为这样会破坏堆的结构

如果想要不破坏堆的结构,我们只需要在某个位置重新向上或者向下调整堆即可,但是这样又会有新的问题,那就是修改或删除元素在堆中的位置是未知的,如果想要获取该位置,则需要重新遍历整个堆,时间复杂度为O(N),很显然时间复杂度过高,为了解决这一问题,则需要实现加强堆

上述无法实现的根本原因就是:没有反向索引表


需要实现的接口:

template<class T, class Compare>
class HeapGreater
{
public:
	HeapGreater()
	{}
	//迭代器初始化,用于利用迭代器往堆中插入元素
	template<class InputIterator>
	HeapGreater(InputIterator first, InputIterator last);
	~HeapGreater()
	{}

	void push(const T& x);
	void pop();

	//更改obj元素的值为target
	void set(const T& obj,const T& target);
	//删除堆中指定的值
	void erase(const T& obj) ;
	T top();//得到堆顶元素
	size_t size();//获取堆中元素个数
	bool empty();//判断堆是否为空
private:
	//需要封装交换函数,因为交换父亲和孩子的值的时候,indexMap中的值也需要交换
	void swap(int i, int j);
	//向上调整算法,每push一个数都要调用向上调整算法,保证插入后仍是一个堆
	void AdjustUp(int child);
	//向下调整算法,每次调用pop都要进行向下调整算法重新构成堆
	void AdjustDwon(int parent);
	//更改某个对象的值,堆结构发生变化,我们需要保持堆结构
	void resign(const T& obj);
	
	vector<T> heap;//堆
	unordered_map<T, int>indexMap;	//反向索引表,记录一个元素在数组中的什么位置,不能使用map,因为key是元素值,如果key相同,不允许冗余
	int heapSize ;//堆的元素个数
};

加强堆和普通堆的区别在于多了一个哈希表 unordered_map<T, int>indexMap,其作用是用来记录堆中元素在vector<T> heap的具体下标位置,只要有了具体的下标位置,我们查找的效率就是O(1),对元素进行修改或者删除就非常容易且高效了

所以:在向上或向下调整的时候,不光vector中的元素要交换,indexMap中对应的映射的下标关系也要交换


注意:如果堆中有重复的元素,就会导致unordered_map<T, int>indexMap中的下标无法正确映射,因为哈希表的key不能重复,即使换成能够重复的unordered_multimap也不行,因为要获取其中一个key的下标时,会与其他相同的key产生冲突

那我们如何解决呢?

  • 那就是传入对象的指针,而不是传入具体的对象,因为对象的地址是不相同的,另外传指针也会比直接传对象节省很多空间

HeapGreater.hpp

#pragma once
#include<iostream>
#include<algorithm>
#include<iostream>
#include<vector>
#include<unordered_map>
#include<assert.h>
using namespace std;

//T:数据类型 Com:比较器
template<class T, class Com>
class HeapGreater
{
public:
	HeapGreater(int sz = 0)
		:heapSize(sz)//初始化堆的元素个数为0
	{}

	//迭代器初始化,用于利用迭代器往堆中插入元素
	template<class InputIterator>
    //使用迭代器区间初始化堆
	HeapGreater(InputIterator first, InputIterator last)
	{
		while (first != last)
		{
			push(*first);//复用push函数
			first++;
		}
	}

	~HeapGreater()
	{
		heap.clear();//清空堆中内容
		heapSize = 0;
	}

    void push(const T& x)
    {
        heap.push_back(x);
        indexMap[x] = heapSize;//在哈希表建立映射关系
        AdjustUp(heapSize++);//在heapSize向上调整,然后个数++
    }
	void pop()
	{
		assert(!empty());	//防止堆为空,复用empty函数
		//堆顶元素和最后一个元素交换,然后删掉最后一个元素,从堆顶位置向下调整
		T& tmp = heap[0];
		swap(0, --heapSize); 
		heap.pop_back();
		AdjustDwon(0);
		//在哈希表中删除tmp的映射
		indexMap.erase(tmp);
	}

	//更改obj的值为target
	void set(const T& obj, const T& target)
	{
		int index = indexMap[obj];//使用哈希表,得到obj在堆中的位置index
		heap[index] = target;//将index位置的值改为target
		//在哈希表中,删除obj的索引,增加target的索引为index位置
		indexMap.erase(obj);
		indexMap[target] = index;
		//调整堆结构,从index位置进行向上调整/向下调整
		//下述代码只执行一个
		AdjustDwon(index);
		AdjustUp(index);
	}

	//删除堆中指定的值
	//要删除的元素和堆中最后一个位置的元素交换,然后调整
	void erase(const T& obj)
	{
		T& tail = heap[heapSize - 1];
		int index = indexMap[obj];//获取obj在堆中的位置index
		indexMap.erase(obj);//删除obj在哈希表的映射
		heapSize--;
		//交换:防止要删的就是最后一个元素!!
		if (obj != tail)
		{
			heap[index] = tail;//堆中index下标的元素变为tail
			indexMap[tail] = index;//为tail在哈希表建立映射
			resign(tail);//调整,相当于原来index位置的值修改为tail
		}
		heap.pop_back();//弹出要被删除的obj元素
	}

	//得到堆顶元素
	T top()
	{
		//防止堆为空
		assert(!empty());//复用empty函数
		return heap[0];
	}

	//获取堆中元素个数
	size_t size()
	{
		return heapSize;
	}

	//判断堆是否为空
	bool empty()
	{
		return heapSize == 0;
	}

private:
	//需要封装交换函数,因为交换父亲和孩子的值的时候,indexMap中的值也需要交换
	//i和j是要交换的两个元素的下标
	void swap(int i, int j) 
	{
		//根据下标得到其对应值
		T h1 = heap[i];
		T h2 = heap[j];
		//交换两个元素的值
		heap[i] = h2;
		heap[j] = h1;
		//更改在哈希表的映射位置
		indexMap[h1] = j;
		indexMap[h2] = i;
	}
	//向上调整算法,每push一个数都要调用向上调整算法,保证插入后仍是一个堆
	void AdjustUp(int child)
	{
		//Com是比较器,模板参数
		Com cmp;
		int parent = (child - 1) / 2;
		//最坏调整到根节点
		while (child)
		{
			if (cmp(heap[parent], heap[child]))
			{
				swap(parent, child);//复用swap函数,传下标,内部会处理
				//迭代往上走
				child = parent;
				parent = (child - 1) / 2;
			}
			else
			{
				break;//已经满足堆结构了
			}
		}
	}
	//向下调整算法,每次调用pop都要进行向下调整算法重新构成堆
	void AdjustDwon(int parent)
	{
		Com cmp;
		int child = parent * 2 + 1;//默认为左孩子
		//最坏调整到叶子
		while (child < heapSize)
		{
			//用比较器选出要的的孩子,要防止没有右孩子
			if (child + 1 < heapSize && cmp(heap[child], heap[child + 1]))
			{
				child++;//要的是右孩子
			}
			if (cmp(heap[parent], heap[child]))
			{
				swap(parent, child);//复用swap函数,传下标,内部会处理
				//迭代往下走
				parent = child;
				child = parent * 2 + 1;
			}
			else
			{
				break;//满足堆结构了
			}
		}
	}

	//更改某个对象的值,堆结构发生变化,我们需要保持堆结构
	void resign(const T& obj)
	{
		//obj对象的值发生改变,要从obj所在堆中的下标位置进行调整
		int index = indexMap[obj];//获取obj对象在堆中的下标
		//执行向上调整 / 向下调整 / 不调整 下面两个代码只执行1个 / 不执行
		AdjustDwon(index);
		AdjustUp(index);
	}

	vector<T> heap;//堆
	unordered_map<T,int> indexMap;	//反向索引表,记录一个元素在数组中的什么位置,不能使用map,因为key是元素值,如果key相同,不允许冗余
	int heapSize;//堆的元素个数
};


//for test
void test_HeapGreater1()
{
	HeapGreater<string, less<string>> heap;
	heap.push("a");
	heap.push("c");
	heap.push("b");
	heap.push("d");
	heap.push("e");
	heap.push("z");
	heap.push("x");

	heap.erase("e");
	heap.set("c", "new c");
	while (!heap.empty())
	{
		cout << heap.top() << endl;
		heap.pop();
	}
}
struct cmp
{
	//比较的对象是string*类型对象
	bool operator()(string*& l, string*& r)
	{
		return *l < *r;//比较string对象,根据ascii比较
	}
};
void test_HeapGreater2()
{
	string* s1 = new string("a");
	string* s2 = new string("c");
	string* s3 = new string("b");
	string* s4 = new string("d");
	string* s5 = new string("e");
	string* s6 = new string("e");
	string* s7 = new string("z");
	string* s8 = new string("new c");

	HeapGreater<string*, cmp> heap;
	heap.push(s1);
	heap.push(s2);
	heap.push(s3);
	heap.push(s4);
	heap.push(s5);
	heap.push(s6);
	heap.push(s7);

	heap.erase(s5);
	heap.set(s2, s8);
	while (!heap.empty())
	{
		cout << *heap.top() << endl;
		heap.pop();
	}
}
void test_HeapGreater3()
{
	vector<string>v = { "a" ,"c" ,"b" ,"d" ,"e" ,"x" ,"z" };
	HeapGreater<string, less<string>> heap(v.begin(), v.end());

	heap.erase("e");
	heap.set("c", "new c");
	while (!heap.empty())
	{

		cout << heap.top() << endl;
		heap.pop();
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/105209.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

安卓手机独有的6个功能,个个都很实用,你用过几个呢?

安卓和苹果&#xff0c;你更喜欢哪一种&#xff1f;今天我整理了6个安卓独有的功能&#xff0c;不看不知道&#xff0c;一看安卓用户可能会吓一跳。因为这些功能真的很实用&#xff0c;但是苹果却没有这些功能。第一种&#xff1a;更改默认打开的应用 苹果系统无法直接更改默认…

【数据库】MVCC

Multi-version Concurrency Control DBMS 维护一个对象的多版本在数据库中。所以事务可以访问历史版本信息。 只依赖MVCC做不到可串行化&#xff1b; 一个事务在改对象的时候&#xff0c;留下一个历史版本&#xff0c;其他的事务可以读这个历史版本的数据。 读者不锁写着&a…

ch3_2多进程中寄存器的切换

运行中的程序在进行切换时&#xff0c; 可以分为以下两类&#xff1a; 线程&#xff1a; 只涉及指令的切换&#xff0c; 硬件资源没有切换&#xff1b;进程&#xff1a; 包含指令的切换&#xff0c;以及硬件资源的切换&#xff0c; 其中映射表便是一种内存资源。 1. 进程间的…

用3Dmax优化模型的方法,让你的效果图又快又好

3DMax是一个特别强大的建模软件&#xff0c;它具有无数需要小伙伴付出时间和练习才能掌握的特性和功能。 如果你已经能足够直观的掌握基础知识&#xff0c;并且已经能创造出很优秀的作品。这必然是一件值得高兴的事。 但是&#xff0c;在设计过程的妙处就在于&#xff0c;总是…

Prometheus集成Grafana(手动创建/通过模板创建Dashboard)

目录1. Grafana的基本介绍2. Centos7上安装Grafana2.1 下载解压2.2 修改conf/default.ini2.3 启动grafana3. Grafana各层级关系4. 添加Prometheus数据源5. 添加Dashborad和手动添加Row和Panel(方式一)6. 通过社区提供的模板创建Dashboard(方式二)1. Grafana的基本介绍 grafana…

自然语言基础之分词提取关键词基本操作

概述 从今天开始我们将开启一段自然语言处理 (NLP) 的旅程. 自然语言处理可以让来处理, 理解, 以及运用人类的语言, 实现机器语言和人类语言之间的沟通桥梁. 关键词 关键词 (keywords), 即关键词语. 关键词能描述文章的本质, 在文献检索, 自动文摘, 文本聚类 / 分类等方面有着…

我发现,被裁的一般都是情商低的人!网友:老板只看你有没有利用价值!

什么样的人容易被裁员&#xff1f;一位网友分享了自己的发现&#xff1a;被裁的一般都是情商比较低的。有人认同楼主&#xff0c;情商高能在一定程度上降低被裁的概率&#xff0c;大多数被裁的员工是因为情商不够&#xff0c;导致人际关系不好&#xff0c;才被辞退。有人说&…

JS如何读取对象内的一个属性两种方法

目录一&#xff1a;读取对象内的一个属性--可以用数组二&#xff1a;读取对象内的一个属性--可以用 .1、点的方式2、中括号的方式一&#xff1a;读取对象内的一个属性–可以用数组 二&#xff1a;读取对象内的一个属性–可以用 . let obj {name:张三,age:18,address:中国}1、…

【PAT甲级 - C++题解】1067 Sort with Swap(0, i)

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4da;专栏地址&#xff1a;PAT题解集合 &#x1f4dd;原题地址&#xff1a;题目详情 - 1067 Sort with Swap(0, i) (pintia.cn) &#x1f511;中文翻译&#xff1a;用 Swap(0, i) 操作…

【关于时间序列的ML】项目 3 :基于机器学习的地震预测模型

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

Datawhale powerful-numpy《从小白到入门》学习笔记

Datawhale powerful-numpy《从小白到入门》学习笔记 持续更新中 文章目录Datawhale powerful-numpy《从小白到入门》学习笔记摘自官方文档的一些话What is NumPy?Why is NumPy Fast?Who Else Uses NumPy?一.创建和生成1.从python列表或元组创建从列表创建从元组创建2.使用ar…

基于ISO13209(OTX)实现引导诊断

在上篇文章《基于ISO13209&#xff08;OTX&#xff09;实现EOL下线序列》中&#xff0c;讲到了OTX的由来以及OTX在EOL中的实现案例&#xff0c;而本文将讲述OTX的另一个广阔应用场景——定义引导诊断序列。 一 何为引导诊断&#xff1f; 引导诊断&#xff0c;通常也称为“引导…

市面上有哪些自动生成报表的工具?

每到月末、季度、年底&#xff0c;就是企业里各营销、财务、运营、企管等各部门“摆数据、晒业绩”的时候&#xff0c;除了财务因为有规范的制度约束&#xff0c;在规则和工具上更有保障&#xff08;最常见的就是在财务软件/ERP 里面自动生成三大周报&#xff09;之外&#xff…

GoPass系列免杀基础(一)

Goby社区第 23 篇插件分享文章 全文共&#xff1a;7730 字 预计阅读时间&#xff1a;20 分钟 小板凳提示&#xff1a;过数字全家桶&#xff08;开启晶核&#xff09;、某绒、卡巴、WD、某管家、魔法保姆、机器人、橘子&#xff0c;全程新手简单版&#xff0c;欢迎师傅们来交流…

一个被迫毕业面试 30 家公司,终于上岸了的Java老前辈的经验分享!

今天分享一个朋友的经历&#xff0c;被“毕业”后的求职经历&#xff1a; 在老东家干了 6 年&#xff0c;发展一般&#xff0c;很想出去&#xff0c;但是一直没有合适的机会&#xff0c;只好一边准备面试一边学习。让我没有想到的是&#xff0c;突然收到了“毕业”通知&#x…

大数据Kudu(十):Flink操作Kudu

文章目录 Flink操作Kudu Flink操作Kudu Flink主要应用场景是流式数据处理上,有些公司针对流式数据使用Flink实时分析后将结果存入Kudu,例如快手公司。这里将实时计算的结果存入Kudu需要自定义Flink Kudu Sink。 场景:Flink实时读取Socket数据,将结果存入Kudu表t_flink_re…

详解OpenCV的椭圆绘制函数ellipse()

函数ellipse()用于在图像上绘制椭圆。 有两种原型&#xff0c;这里只列出常用的那种。 C原型如下&#xff1a; void cv::ellipse(InputOutputArray img,Point center,Size axes,double angle,double startAngle,double endAngle,const Scalar & color,int thickness 1,…

地统计插值学习心得(三)ArcGIS Pro与ArcMap软件中地统计分析的区别

前言 ArcMap中地统计分析由来已久,很多GIS专业的同学学习地统计内容都是在ArcMap软件中实现的,随着IT技术的发展,ArcGIS系列软件架构也发生了重大变化,传统的ArcMap软件已不太能够满足当前的应用需求,在此背景下,ESRI推出了64位的ArcGIS Pro桌面软件,来实现二三维一体化…

【关于时间序列的ML】项目 4 :使用机器学习预测迁移

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

【关于2022年卡塔尔世界杯】

2022卡塔尔世界杯最全面的看点和分析&#xff0c;相信一定有你感兴趣的一点&#xff0c;相信一定会有你感兴趣的&#xff0c;推荐点赞收藏~~ 2022年世界杯比以往任何时候都晚&#xff0c;因为卡塔尔太热了…… 然而&#xff0c;四年一度的世界杯终于……来了 今年的世界杯&am…