【分治算法】续:快速选择和堆选择两种算法解决选择第k小问题

第K小算法

方法一:基于快速排序的选择方法

方法一:基于快速排序的选择方法
思路和算法
「划分」 过程是:从子数组 a[l⋯r] 中选择任意一个元素 x 作为主元,调整子数组的元素使得左边的元素都小于等于它,右边的元素都大于等于它, x 的最终位置就是 q,且每次经过「划分」操作后,我们一定可以确定一个元素的最终位置,即 x 的最终位置为 q,并且保证 a[l⋯q−1] 中的每个元素小于等于 a[q],且 a[q] 小于等于 a[q+1⋯r] 中的每个元素。所以只要某次划分的 q为倒数第 k 个下标的时候,我们就得到了结果,我们并不关注a[l⋯q−1] 和 a[q+1⋯r] 是否是有序。

inline int partition(vector<int>& arr, int l, int r) 
{//partition,分组,返回arr[i]的准确排序位置
    int x = arr[r], i = l - 1;
    for (int j = l; j < r; ++j) {
        if (arr[j] <= x) {
            swap(arr[++i], arr[j]);
        }
    }
    swap(arr[i + 1], arr[r]);
    return i + 1;
}

inline int randomPartition(vector<int>& arr, int l, int r) 
{//随机取一个数,返回其准确位置
    int i = rand() % (r - l + 1) + l;//随机取一个arr[i]数
    swap(arr[i], arr[r]);//作为游标
    return partition(arr, l, r);//返回arr[i]的准确位置,不关注arr[l...i-1]和arr[i+1...r]是否有序
}


int quickSelect(vector<int>& arr, int l, int r, int index) 
{//返回第k小的值
    int q = randomPartition(arr, l, r);
    if (q == index) 
    {
        return arr[q];
    } else 
    {
        return q < index ? quickSelect(arr, q + 1, r, index) : quickSelect(arr, l, q - 1, index);
    }
}

int quick_findKthSmallest(vector<int>& nums, int k) 
{//调用选择函数,容器元素从0位置开始存储,最小是0
    srand(time(0));
    return quickSelect(nums, 0, nums.size() - 1, k);
}                                                

方法二:基于堆排序的选择方法

在这里插入图片描述

思路和算法

使用堆排序来解决这个问题——建立一个大根堆,做 k−1 次删除操作后堆顶元素就是我们要找的答案。

void maxHeapify(vector<int>& a, int i, int heapSize)
{//调整堆
    int l = i * 2 + 1, r = i * 2 + 2, largest = i;
    if (l < heapSize && a[l] > a[largest]) 
    {
        largest = l;
    } 
    if (r < heapSize && a[r] > a[largest]) 
    {
        largest = r;
    }
    if (largest != i) 
    {
        swap(a[i], a[largest]);
        maxHeapify(a, largest, heapSize);
    }
}

void buildMaxHeap(vector<int>& a, int heapSize) 
{//建堆
    for (int i = heapSize / 2; i >= 0; --i) 
    {
        maxHeapify(a, i, heapSize);
    } 
}

int heap_findKthSmallest(vector<int>& nums, int k) 
{//获得堆顶最小值,最小是1
    int heapSize = nums.size();
    buildMaxHeap(nums, heapSize);
    for (int i = nums.size() - 1; i >=k ; --i) 
    {
        swap(nums[0], nums[i]);
        --heapSize;
        maxHeapify(nums, 0, heapSize);
    }
    return nums[0];
}

完整代码

/*
 * @Description: 
 * @Author: dive668
 * @Date: 2021-06-03 10:38:58
 * @LastEditTime: 2021-06-03 21:30:06
 */
#include <iostream>
#include <vector>
#include <sstream>
#include <string>
#include <fstream>
#include <ctime>
#include <algorithm>
using namespace std;


inline int partition(vector<int>& arr, int l, int r) 
{//partition,分组,返回arr[i]的准确排序位置
    int x = arr[r], i = l - 1;
    for (int j = l; j < r; ++j) {
        if (arr[j] <= x) {
            swap(arr[++i], arr[j]);
        }
    }
    swap(arr[i + 1], arr[r]);
    return i + 1;
}

inline int randomPartition(vector<int>& arr, int l, int r) 
{//随机取一个数,返回其准确位置
    int i = rand() % (r - l + 1) + l;//随机取一个arr[i]数
    swap(arr[i], arr[r]);//作为游标
    return partition(arr, l, r);//返回arr[i]的准确位置,不关注arr[l...i-1]和arr[i+1...r]是否有序
}


int quickSelect(vector<int>& arr, int l, int r, int index) 
{//返回第k小的值
    int q = randomPartition(arr, l, r);
    if (q == index) 
    {
        return arr[q];
    } else 
    {
        return q < index ? quickSelect(arr, q + 1, r, index) : quickSelect(arr, l, q - 1, index);
    }
}


int quick_findKthSmallest(vector<int>& nums, int k) 
{//调用选择函数,容器元素从0位置开始存储,最小是0
    srand(time(0));
    return quickSelect(nums, 0, nums.size() - 1, k);
}
int str2int(string s)
{//将字符串转换为int,用于从文件中读取内容存储到容器中
    int temp;
    stringstream ss;
    ss<<s;
    ss>>temp;
    return temp;
}
vector<int> file_getnum(string filename)
{//读取数据集文件,返回数组容器
    vector<int> vec;
    string s1;
    int temp;
    ifstream fp(filename.c_str(),ios::in);
   	if(fp)
	{
		while(!fp.eof())
		{
			getline(fp,s1);
			vec.push_back(str2int(s1));
		}
	}
	else cout<<"file open error!"<<endl;
    fp.close();
    return vec;
}
void maxHeapify(vector<int>& a, int i, int heapSize)
{//调整堆
    int l = i * 2 + 1, r = i * 2 + 2, largest = i;
    if (l < heapSize && a[l] > a[largest]) 
    {
        largest = l;
    } 
    if (r < heapSize && a[r] > a[largest]) 
    {
        largest = r;
    }
    if (largest != i) 
    {
        swap(a[i], a[largest]);
        maxHeapify(a, largest, heapSize);
    }
}

void buildMaxHeap(vector<int>& a, int heapSize) 
{//建堆
    for (int i = heapSize / 2; i >= 0; --i) 
    {
        maxHeapify(a, i, heapSize);
    } 
}

int heap_findKthSmallest(vector<int>& nums, int k) 
{//获得堆顶最小值,最小是1
    int heapSize = nums.size();
    buildMaxHeap(nums, heapSize);
    for (int i = nums.size() - 1; i >=k ; --i) 
    {
        swap(nums[0], nums[i]);
        --heapSize;
        maxHeapify(nums, 0, heapSize);
    }
    return nums[0];
}

int main()
{
    string filename;
	string filename_new;
    cout << "输入要读取的文件名字(example:test0):";
    cin >> filename;
    int k=0;
    cout<<"给出要找的最小的k:";
    cin>>k;
    filename_new=filename+".txt";
	clock_t t;
    vector<int> nums;
    vector<int>::iterator p;
    nums=file_getnum(filename_new);
    cout<<"开始输出"<<endl;
    cout<<heap_findKthSmallest(nums,k)<<endl;
    cout<<"输出结束"<<endl;
    return 0;
}                     

测试数据集,对比两种 k 位数算法

(1)随机生成大小不同数据集合,如大小分别为(1000,2000,5000,10000,20000, 50000, 100000)的数据集合;
(2)对每个算法,针对不同大小的数据集合,运行算法 1000 次并记录算法运行的平均时间;
(3)将得到的运行时间绘制成曲线,分析并比较两种不同的算法。

随机数数据集生成
/*
 * @Description: 
 * @Author: dive668
 * @Date: 2021-06-03 22:09:28
 * @LastEditTime: 2021-06-03 22:19:31
 */
#include <iostream>
#include <sstream>
#include <cstring>
#include <ctime>
#include <algorithm>
using namespace std;
const int MAX = 1e3;
int* generate_num(int percent)//参数是重复率 0~10
{
    FILE *fp;
    fp=freopen("test_1000.txt","w",stdout);//设置 cout printf 这些输出流都输出到 test.txt里面去
    int *x = new int[MAX+1]();
    int *num = new int[MAX + 1]();
    int i=1;
    int temp = 0;
    if(percent==0)
    {
        for (i = 1; i <= MAX;++i)
        {
            x[i] = i;
        }
        std::random_shuffle(x+1,x+MAX+1);//打乱
        cout << x[1];
        for (i = 2; i <= MAX;++i)
        {
            cout << endl<< x[i];
        }
        fclose(fp);
        return x;
    }
    else
    {
        srand(time(0));
        for (i = 1; i <= MAX - MAX * 10 * percent * 0.01;++i)
        {
            x[i]=i;
            num[i]++;            
        }
        while(true)
        {
            for (i = 1; i <= MAX;++i)
            {
                temp=i;
                if(num[temp]==0) break;
            }
            break;
        }
        for (i = MAX - MAX * 10 * percent * 0.01 + 1; i <= MAX;++i)
        {
            x[i] = temp;
        }
        std::random_shuffle(x+1,x+MAX+1);//打乱
        for(i=1;i<=MAX;++i)
        {
            if(i==1)
                cout<<x[i];
            else
                cout<<endl<<x[i];
        }
        fclose(fp);
        return x;
    }    
}

int main()
{
    int *x = new int[MAX + 1]();
    int percent;
    cout << "输入重复比率(0~10):";
    cin >> percent;
    x = generate_num(percent);
    //恢复标准输入输出
    freopen("CON","r",stdin);  
    freopen("CON","w",stdout);
    cout << "生成成功" << endl;
    return 0;
}

正确解决 VS Code / GCC / Clang 编译时中文乱码问题
在这里插入图片描述

随机数数据集选择排序1000次运行时间比较
/*
 * @Description: 
 * @Author: dive668
 * @Date: 2021-06-03 10:38:58
 * @LastEditTime: 2021-06-03 22:47:37
 */
#include <iostream>
#include <vector>
#include <sstream>
#include <string>
#include <fstream>
#include <ctime>
#include <algorithm>
using namespace std;


inline int partition(vector<int>& arr, int l, int r) 
{//partition,分组,返回arr[i]的准确排序位置
    int x = arr[r], i = l - 1;
    for (int j = l; j < r; ++j) {
        if (arr[j] <= x) {
            swap(arr[++i], arr[j]);
        }
    }
    swap(arr[i + 1], arr[r]);
    return i + 1;
}

inline int randomPartition(vector<int>& arr, int l, int r) 
{//随机取一个数,返回其准确位置
    int i = rand() % (r - l + 1) + l;//随机取一个arr[i]数
    swap(arr[i], arr[r]);//作为游标
    return partition(arr, l, r);//返回arr[i]的准确位置,不关注arr[l...i-1]和arr[i+1...r]是否有序
}


int quickSelect(vector<int>& arr, int l, int r, int index) 
{//返回第k小的值
    int q = randomPartition(arr, l, r);
    if (q == index) 
    {
        return arr[q];
    } else 
    {
        return q < index ? quickSelect(arr, q + 1, r, index) : quickSelect(arr, l, q - 1, index);
    }
}


void quick_findKthSmallest(vector<int>& nums, int k) 
{//调用选择函数,容器元素从0位置开始存储,最小是0
    srand(time(0));
    quickSelect(nums, 0, nums.size() - 1, k);
}
int str2int(string s)
{//将字符串转换为int,用于从文件中读取内容存储到容器中
    int temp;
    stringstream ss;
    ss<<s;
    ss>>temp;
    return temp;
}
vector<int> file_getnum(string filename)
{//读取数据集文件,返回数组容器
    vector<int> vec;
    string s1;
    int temp;
    ifstream fp(filename.c_str(),ios::in);
   	if(fp)
	{
		while(!fp.eof())
		{
			getline(fp,s1);
			vec.push_back(str2int(s1));
		}
	}
	else cout<<"file open error!"<<endl;
    fp.close();
    return vec;
}
void maxHeapify(vector<int>& a, int i, int heapSize)
{//调整堆
    int l = i * 2 + 1, r = i * 2 + 2, largest = i;
    if (l < heapSize && a[l] > a[largest]) 
    {
        largest = l;
    } 
    if (r < heapSize && a[r] > a[largest]) 
    {
        largest = r;
    }
    if (largest != i) 
    {
        swap(a[i], a[largest]);
        maxHeapify(a, largest, heapSize);
    }
}

void buildMaxHeap(vector<int>& a, int heapSize) 
{//建堆
    for (int i = heapSize / 2; i >= 0; --i) 
    {
        maxHeapify(a, i, heapSize);
    } 
}

void heap_findKthSmallest(vector<int>& nums, int k) 
{//获得堆顶最小值,最小是1
    int heapSize = nums.size();
    buildMaxHeap(nums, heapSize);
    for (int i = nums.size() - 1; i >=k ; --i) 
    {
        swap(nums[0], nums[i]);
        --heapSize;
        maxHeapify(nums, 0, heapSize);
    }
}

int main()
{
    string filename;
	string filename_new;
    cout << "输入要读取的文件名字(example:test0):";
    cin >> filename;
    int k=0;
    cout<<"给出要找的最小的k:";
    cin>>k;
    clock_t time;
    int temp;
    filename_new=filename+".txt";
	clock_t t;
    vector<int> nums;
    vector<int>::iterator p;
    nums=file_getnum(filename_new);
    cout<<"开始运行快速选择"<<endl;
    t=clock();
    for(int i=0;i<1000;++i)
        quick_findKthSmallest(nums,k);
    cout<<"运行时间"<<clock()-t<<endl;
    cout<<"------------------"<<endl;
    cout<<"开始运行堆选择"<<endl;
    t=clock();
    for(int i=0;i<1000;++i)
        heap_findKthSmallest(nums,k);
    cout<<"运行时间"<<clock()-t<<endl;
    return 0;
}                               

在这里插入图片描述
在这里插入图片描述

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值