求取一组无序数组中第k大的数

方法1.:维持一个大小为k最小堆,

  1. 后面来的数小或者等于堆顶元素,则跳过,;
  2. 后面来的数大于堆顶元素,堆顶元素弹出,新元素加入最小堆

 

最后留下的k个数就是,所有数中前k大的数,堆顶元素就是第k大的数

时间复杂度:由于维持大小为k的堆花费时间为log(k),所以时间复杂度为nlog(k).

代码如下:

//构建最小堆,当前数比父结点小就往上冒
void bulidMinHeap(int arr[], int index) {
	while (arr[index] < arr[(index - 1) / 2]) {
		swap(arr[index], arr[(index - 1) / 2]);
		index = (index - 1) / 2;
	}
}


//维持堆的结构,选取出当前数与其左右孩子中最小的数
//如果最小数为自身,退出
//如果为左右孩子,与其交换
void heapify(int arr[], int index, int heapSize) {
	int leftChild = index * 2 + 1;
	int smallestIndex;
	while (leftChild < heapSize) {
		smallestIndex = leftChild + 1 < heapSize && arr[leftChild] > arr[leftChild + 1]
			? leftChild + 1 : leftChild;
		smallestIndex = arr[index] > arr[smallestIndex] ? smallestIndex : index;
		if (smallestIndex == index){
			break;
		}
		swap(arr[index], arr[smallestIndex]);
		index = smallestIndex;
		leftChild = index * 2 + 1;
	}
}


int calKthMin(int arr[], int k, int arrSize) {
	//建立大小为k的最小堆
	for (int i = 0; i < k; i++){
		bulidMinHeap(arr, i);
	}
	//对后面的数进行处理
	for (int i = k; i < arrSize; i++){
		if (arr[i] <= arr[0]) continue;
		else {
			swap(arr[0], arr[i]);
			heapify(arr, 0, k);
		}
	}
	return arr[0];
}

使用c++STL代码如下;

注意:优先队列默认为less<int>参数,less越来越小之意,即最大堆

//priority_queue<int, vector<int>, greater<int>> minHeap;
//使用greater<int> 要加头文件#include <functional>
//priority_queue<int, vector<int>, less<int>> maxHeap;
int calKthMaxByQueue(int arr[], int k, int arrSize) {
	priority_queue<int, vector<int>, greater<int>> minHeap;
	for (int i = 0; i < k; i++) {
		minHeap.push(arr[i]);
	}
	for (int i = k; i < arrSize; i++){
		if (arr[i] <= minHeap.top()) continue;
		else {
			minHeap.pop();
			minHeap.push(arr[i]);
		}
	}
	return minHeap.top();
}

方法2:利用快排中partition函数的思想,一直partition到等于轴心数的区域包含arr[arrSize - k]

假设求第100大的数,那个排序之后该数就该在数组中的下标就该为1000 - 100 = 900 

时间复杂度:每次选择的pivotNum数的好坏将影响时间,长期期望为O(n);

//partition过程,返回新子区间的上下界
vector<int> partition(int arr[], int leftIndex, int rightIndex) {
	int lessBoundary = leftIndex - 1;
	int biggerBoundary = rightIndex + 1;
	int curIndex = leftIndex;

	int randomIndex = rand() % (rightIndex - leftIndex + 1) + leftIndex;
	swap(arr[randomIndex], arr[rightIndex]);
	int pivotNum = arr[rightIndex];

	while (curIndex < biggerBoundary) {
		if (arr[curIndex] < pivotNum) {
			swap(arr[curIndex++], arr[++lessBoundary]);
		}
		else if (arr[curIndex] > pivotNum) {
			swap(arr[curIndex], arr[--biggerBoundary]);
		}
		else {
			curIndex++;
		}
	}
	vector<int> leftRightIndex(2);
	leftRightIndex[0] = lessBoundary;
	leftRightIndex[1] = biggerBoundary;
	return leftRightIndex;
}


//kthIndex:第k大的数在数组中应该在的位置
//equalRange为本次partition得到的等于x的区间
//kthIndex在equalRange中停止,否则去子区间寻找
int process(int arr[], int kthIndex, int leftIndex, int rightIndex) {
	vector<int> equalRange = partition(arr, leftIndex, rightIndex);
	if (kthIndex < equalRange[1] && kthIndex > equalRange[0]) {
		return arr[kthIndex];
	}
	else if (kthIndex <= equalRange[0]) {
		return process(arr, kthIndex, leftIndex, equalRange[0]);
	}
	else{
		return process(arr, kthIndex, equalRange[1], rightIndex);
	}
}

int calKthMaxByPartition(int arr[], int k, int arrSize) {
	return process(arr, arrSize - k, 0, arrSize - 1);
}

 

可以使用快速选择算法来解决这个问题,其时间复杂度为O(N)。 快速选择算法基于快速排序,但不需要完全排序整个数组。它通过每次选择一个基准元素并将数组分为两个部分来工作,然后根据基准元素的位置来确定我们需要搜索的那个部分。 具体地,我们可以首先随机选择一个元素作为基准元素,然后将数组中比该元素小的元素放在它的左边,比该元素的元素放在它的右边。假设基准元素的位置为p,则左边的子数组中有p个元素小于它,右边的子数组中有n-p-1个元素于它。如果k=p,则基准元素即为第k小的元素;如果k<p,则我们需要在左边的子数组中继续搜索第k小的元素;如果k>p,则我们需要在右边的子数组中继续搜索第k-p-1小的元素。 下面是使用快速选择算法求解第k小的的C代码: ``` #include <stdio.h> #include <stdlib.h> int partition(int* nums, int left, int right) { int pivot = nums[left]; int i = left + 1, j = right; while (i <= j) { while (i <= j && nums[i] < pivot) { i++; } while (i <= j && nums[j] >= pivot) { j--; } if (i < j) { int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } } int temp = nums[left]; nums[left] = nums[j]; nums[j] = temp; return j; } int quickSelect(int* nums, int left, int right, int k) { if (left == right) { return nums[left]; } int pivotIndex = partition(nums, left, right); if (k == pivotIndex) { return nums[k]; } else if (k < pivotIndex) { return quickSelect(nums, left, pivotIndex - 1, k); } else { return quickSelect(nums, pivotIndex + 1, right, k); } } int findKthSmallest(int* nums, int size, int k) { return quickSelect(nums, 0, size - 1, k - 1); } int main() { int nums[] = {3, 2, 1, 5, 6, 4}; int size = sizeof(nums) / sizeof(int); int k = 2; int kthSmallest = findKthSmallest(nums, size, k); printf("The %dth smallest element is %d\n", k, kthSmallest); return 0; } ``` 在这个例子中,我们使用数组{3, 2, 1, 5, 6, 4}来演示如何找到第2小的元素。我们首先调用findKthSmallest函,并将nums数组小和k值作为参传递。在findKthSmallest函中,我们调用quickSelect函来实际查找第k小的元素。在quickSelect函中,我们首先将left和right参传递给partition,以便将数组分为两个部分。然后,我们根据pivotIndex的值决定搜索哪个子数组。如果k等于pivotIndex,则我们找到了第k小的元素;否则,我们递归调用quickSelect函来搜索左边或右边的子数组。最后,我们在main函中打印出找到的第k小的元素。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值