跳转至内容
  • 版块
  • 最新
  • 标签
  • 热门
  • Online Tools
  • 用户
  • 群组
折叠
品牌标识

D2Learn Forums

  1. 主页
  2. Blogs | 博客
  3. Maxwell1905's Blog
  4. 通过快排模拟IDE上qosrt的实现方法

通过快排模拟IDE上qosrt的实现方法

已定时 已固定 已锁定 已移动 Maxwell1905's Blog
c语言blog
2 帖子 2 发布者 23 浏览
  • 从旧到新
  • 从新到旧
  • 最多赞同
登录后回复
此主题已被删除。只有拥有主题管理权限的用户可以查看。
  • sky-littlestarS 离线
    sky-littlestarS 离线
    sky-littlestar
    编写于 最后由 编辑
    #1

    前言

     排序是编程中经常用到的数据处理方法,比如说我们可能要对实验数据进行分析,对高考成绩进行排名,在嵌入式领域中,常常要对MCU中ADC采集的数据进行排序处理,具有高频出现的特征,所以好的排序方法对于数据处理的准确,提升程序运行效率具有重要的意义。一般我们常用的排序方法有一下几种,插入排序,选择排序,冒泡排序,快速排序等等,其中Visual Studio提供了快速排序的API,我们可以方便调用qsort,但是我们点进去函数定义时可以发现,VS只提供了接口,而没有展示具体实现,下面,我尝试通过我对排序算法的一些理解,模拟一下qsort的实现。
    c4023616-3cf9-4555-95e2-270c8da764d1-image.png

    qsort原理介绍

     排序目前主要有以下几种算法,插入排序,选择排序,冒泡排序,快速排序算法,其中插入排序,选择排序,冒泡排序算法的时间复杂度为O(n^2),在处理大量数据时效率不高,而快速排序的时间复杂度比较低,为O(log2N),所以我选择通过快速排序算法,模拟Visual Studio IDE中的实现。快速排序采用的是分而治之的思想,每次从数组中取一个元素作为基准值,将小于基准值的元素放左边,大于基准值的元素放右边,通过对左,右区域进行迭代操作,最终实现排序。示意图如下:
    20a978b7-c9b7-4c16-aaf9-99616cfd30cb-image.png ![51b3f240-6478-44f3-9065-0e0f08c2c334-image.png]
    先确定本次排序的数组边界,分别是left,right,随后初始化指针L,R,分别指向本轮迭代的边界,将比较基准设置为数组第一个元素,在本轮迭代中,base=4,随后开始查找数组元素交换,将R向左移动,寻找比base小的数据,将L向右移动,寻找比base大的数据,当两者都找到合适的数据后,互相交换,即可实现排序。
    a03e7fe4-a610-482e-9170-9fc1a2c778da-image.png
    交换
    5e7e5b7d-b4c5-4112-a8ed-ae16da52229d-image.png
    继续查找下一组合适的元素
    02415e27-83d2-4d15-b68a-383a3f5169e8-image.png
    交换
    7e3d12b5-6f0f-4d50-8731-9f88a2b5283d-image.png
    依此类推(注意是要先移动右边的R指针,确定其符合要求再移动左边的指针)
    3c504dc5-a48a-4ace-a9f9-a2318a4be980-image.png
    此时右移动L指针,L和R重叠,无法满足交换条件,证明R指针右边的数都比Base大,于是交换Base和R,进行下一轮排序
    bed039de-355c-4730-9e80-42d681628395-image.png
    bd94fd25-bef1-40b9-9d6a-a3ff36091f23-image.png
    通过不断迭代,我们每次将排序范围缩少一般,所以经过O(log2N)次,我们即可实现排序。
    代码具体实现如下

    void my_qsort(uint8_t array[], uint8_t left, uint8_t right)
    {
    	int base = array[left];
    	uint8_t L = left;
    	uint8_t R = right;
    
    	while (L < R)
    	{
    		while ((array[R] > base)&&L<R)
    		{
    			R--;
    		}
    
    		while ((array[L] <= base)&&L<R)
    		{
    			L++;
    		}
    
    		uint8_t temp;
    		temp = array[R];
    		array[R] = array[L];
    		array[L] = temp;
    	}
    
    	uint8_t temp;
    
    	temp = array[L];
    	array[L] = array[left];
    	array[left] = temp;
    
    	
    	if(left<L)
    		my_qsort( array, left, L-1);
    	if(R<right)
    		my_qsort( array, R+1, right);
    }
    
    int main()
    {
    	uint8_t array[10] = { 0 };
    	srand((unsigned)time(NULL));
    	for (int i = 0; i < 10; i++)
    	{
    		array[i] = i+1;
    	}
    
    	for (int i = 0; i < 5; i++)  //生成无序随机数
    	{
    		uint8_t j = rand() % 10;
    		uint8_t k = rand() % 10;
    		uint8_t temp;
    
    		temp = array[k];
    		array[k] = array[j];
    		array[j] = temp;
    	}
    	printf("data:");
    	for (int i = 0; i < 10; i++)
    	{
    		printf("%d ", array[i]);
    	}
    	printf("\r\n");
    	
    	my_qsort(array,0,9);    //进行排序
    	printf("after sort:");
    	for (int i = 0; i < 10; i++)
    	{
    		printf("%d ", array[i]);
    	}
    }
    

    看看运行效果
    f2e4580f-8fc3-4c33-bdc1-01e4fb27184d-image.png
    但是,这个函数形式跟Visual Studio提供了快速排序的API还有较大区别,探索完原理后我们还需要将快排算法,写成Visual Studio提供API的形式。

    qsort的具体实现

    _ACRTIMP void __cdecl qsort(
    Inout_updates_bytes(_NumOfElements * _SizeOfElements) void* _Base,
    In size_t _NumOfElements,
    In size_t _SizeOfElements,
    In _CoreCrtNonSecureSearchSortCompareFunction _CompareFunction
    );

    首先,我们观察qsort的传参部分,其参数有未定义类型指针Base(指向需要比较元素的地址),_NumOfElements(需要比较的元素数量),_SizeOfElements(元素的字节大小),_CompareFunction(函数指针,确定元素的比较方法)
    将我们自己编写的my_qsort和qsort对比即可知道,我们如何改写自己的函数。
    观察void my_qsort(uint8_t array[], uint8_t left, uint8_t right),参数指针array(指向需要比较的元素地址),left(需要比较的起始元素下标),right(需要比较元素的终止下标),其实到这里,就比较清晰了,参数array跟base其实是一样的,指代比较元素的内存地址所在,_NumOfElements=right-left,指代需要比较元素的数量,_SizeOfElements指元素字节的大小,由于我们自己写的my_qsort是指定单字节的数字比较,可是在实践中,我们常常会有uint16_t,uint32_t甚至是float的比较需求,所以我们可以看到IDE提供的API还是有很大的灵活性,可以对比各种元素,_CompareFunction指元素的对比规则,是升序还是降序,我们下面可以结合具体代码进行分析。
    my_qsort是这样定义基准的:
    int base = array[left];
    uint8_t L = left;
    uint8_t R = right;

    将其转换为qsort可以这样写,
    uint8_t L = 0;
    uint8_t R = numofElement-1;
    这样可以同样得到,L和R指针的下标,随后进行比较

    while (L < R)
    	{
    		while ((array[R] > base)&&L<R)
    		{
    			R--;
    		}
    
    		while ((array[L] <= base)&&L<R)
    		{
    			L++;
    		}
    
    		uint8_t temp;
    		temp = array[R];
    		array[R] = array[L];
    		array[L] = temp;
    	}
    

    可以这样改写

    while (L < R)
    {
    	while (CompareFunction((unsigned char*)base + (R * sizeofElement), (unsigned char*)base)>0 && L < R) //array[R] > base
    	{
    		R--;
    	}
    
    	while (CompareFunction((unsigned char*)base + (L * sizeofElement), (unsigned char*)base) <= 0 && L < R) //array[L] <= base
    	{
    		L++;
    	}
    
    	for (int i = 0; i < sizeofElement; i++)
    	{
    		unsigned char  temp = 0;
    		unsigned char* buf1 = (unsigned char*)base + (L * sizeofElement)+i;
    		unsigned char* buf2 = (unsigned char*)base + (R * sizeofElement)+i;
    		temp = *buf1;
    		*buf1 = *buf2;
    		*buf2 = temp;
    	}
    }
    

    从这里可以看到,sizeofElement的作用就发挥出来了,我们自己实现的my_qsort是array[R] > base进行比较,但是我们这里比较只知道起始地址base和字节大小sizeofElement,但是我们知道数据地址和数据类型,我们就可以求得其元素,我们平时的指针操作就是这样的。
    ac66fc07-b408-4561-878e-93daf59a6963-image.png
    c608e4aa-e495-411e-8c79-9fa5b8b909bc-image.png
    当指针p1指向address:01时,并按照uint16_t进行解释,就是把两个字节的内存数据进行整合分析,所以打印结果是0x1234,当指针p2指address:01时,并按照uint8_t进行解释时,解释的是单字节,所以打印结果是0x34,当p2在内存移动,p2+1时,p2指向address:02地址时,解释为0x12,当然,数据在内存上的分布,跟硬件的大小端有关,这里不展开解释,所以通过(地址+数据类型)的方法,我们可以得到数据大小,并进行比较,而不用像我们的my_qsort函数一样只能固定比较无符号8位数。
    9bfaa4b2-9bf1-45f2-bc33-10c2519d7476-image.png
    起始地址为base,由于我们的数据是300已经超过了uint8_t的范围,所以只能采用uint16_t,所以,一个数据占用2个字节,即是sizeofElement为2,base=301,R指针指向最后一个数300,其地址为base + (3 * sizeofElement),下标为3,L指针指向306,其地址为base + (1* sizeofElement),跟我们用数组其实是一个寻址原理,然后进行交换

    for (int i = 0; i < sizeofElement; i++)
    	{
    		unsigned char  temp = 0;
    		unsigned char* buf1 = (unsigned char*)base + (L * sizeofElement)+i;
    		unsigned char* buf2 = (unsigned char*)base + (R * sizeofElement)+i;
    		temp = *buf1;
    		*buf1 = *buf2;
    		*buf2 = temp;
    	}
    

    我们交换通过逐字节交换,数量为sizeofElement,最终实现数组元素交换
    62e236d1-854c-4655-8d57-bdea47814174-image.png
    随后,进行迭代递归,即可完成,下面的函数完整代码

    void test_qsort(void* base,uint8_t numofElement,uint8_t sizeofElement, strcmp_fun CompareFunction)
    {
    	uint8_t L = 0;
    	uint8_t R = numofElement-1;
    
    	while (L < R)
    	{
    		while (CompareFunction((unsigned char*)base + (R * sizeofElement), (unsigned char*)base)>0 && L < R) //array[R] > base
    		{
    			R--;
    		}
    
    		while (CompareFunction((unsigned char*)base + (L * sizeofElement), (unsigned char*)base) <= 0 && L < R) //array[L] <= base
    		{
    			L++;
    		}
    
    		for (int i = 0; i < sizeofElement; i++)
    		{
    			unsigned char  temp = 0;
    			unsigned char* buf1 = (unsigned char*)base + (L * sizeofElement)+i;
    			unsigned char* buf2 = (unsigned char*)base + (R * sizeofElement)+i;
    			temp = *buf1;
    			*buf1 = *buf2;
    			*buf2 = temp;
    		}
    	}
    
    	for (int i = 0; i < sizeofElement; i++)
    	{
    		unsigned char  temp = 0;
    		unsigned char* buf1 = (unsigned char*)base + (L * sizeofElement) + i;
    		unsigned char* buf2 = (unsigned char*)base+i;
    		temp = *buf1;
    		*buf1 = *buf2;
    		*buf2 = temp;
    	}
    
    
    	if (base < (unsigned char*)base + (L * sizeofElement))
    		test_qsort(base,L , sizeofElement, CompareFunction);
    	if ((unsigned char*)base + (R * sizeofElement) < (unsigned char*)base + (numofElement-1) * sizeofElement)
    		test_qsort((unsigned char*)base+(R+1)* sizeofElement, numofElement-L-1,sizeofElement, CompareFunction);
    }
    

    我们在IDE上运行测试一下代码:

    typedef int (*strcmp_fun)(const void*,const void*);
    int CompareFunction(const void*, const void*);
    void test_qsort(void* base, uint8_t numofElement, uint8_t sizeofElement, strcmp_fun CompareFunction);
    int main()
    {
    	uint16_t array[10] = { 0 };
    	srand((unsigned)time(NULL));
    	for (int i = 0; i < 10; i++)
    	{
    		array[i] =rand()%1000;
    	}
    
    	for (int i = 0; i < 5; i++)  //生成无序随机数
    	{
    		uint8_t j = rand() % 10;
    		uint8_t k = rand() % 10;
    		uint8_t temp;
    
    		temp = array[k];
    		array[k] = array[j];
    		array[j] = temp;
    	}
    	printf("data:");
    	for (int i = 0; i < 10; i++)
    	{
    		printf("%d ", array[i]);
    	}
    	printf("\r\n");
    
    	test_qsort(array, sizeof(array)/sizeof(uint16_t),sizeof(uint16_t), CompareFunction);    //进行排序
    	printf("after sort:");
    	for (int i = 0; i < 10; i++)
    	{
    		printf("%d ", array[i]);
    	}
    
    }
    
    int CompareFunction(const void* a, const void* b)
    {
    	if (*(uint16_t*)a - *(uint16_t*)b > 0)
    		return 1;
    	else
    		return 0;
    }
    
    void test_qsort(void* base, uint8_t numofElement, uint8_t sizeofElement, strcmp_fun CompareFunction)
    {
    	uint8_t L = 0;
    	uint8_t R = numofElement - 1;
    
    	while (L < R)
    	{
    		while (CompareFunction((unsigned char*)base + (R * sizeofElement), (unsigned char*)base) > 0 && L < R) //array[R] > base
    		{
    			R--;
    		}
    
    		while (CompareFunction((unsigned char*)base + (L * sizeofElement), (unsigned char*)base) <= 0 && L < R) //array[L] <= base
    		{
    			L++;
    		}
    
    		for (int i = 0; i < sizeofElement; i++)
    		{
    			unsigned char  temp = 0;
    			unsigned char* buf1 = (unsigned char*)base + (L * sizeofElement) + i;
    			unsigned char* buf2 = (unsigned char*)base + (R * sizeofElement) + i;
    			temp = *buf1;
    			*buf1 = *buf2;
    			*buf2 = temp;
    		}
    	}
    
    	for (int i = 0; i < sizeofElement; i++)
    	{
    		unsigned char  temp = 0;
    		unsigned char* buf1 = (unsigned char*)base + (L * sizeofElement) + i;
    		unsigned char* buf2 = (unsigned char*)base + i;
    		temp = *buf1;
    		*buf1 = *buf2;
    		*buf2 = temp;
    	}
    
    
    	if (base < (unsigned char*)base + (L * sizeofElement))
    		test_qsort(base, L, sizeofElement, CompareFunction);
    	if ((unsigned char*)base + (R * sizeofElement) < (unsigned char*)base + (numofElement - 1) * sizeofElement)
    		test_qsort((unsigned char*)base + (R + 1) * sizeofElement, numofElement - L - 1, sizeofElement, CompareFunction);
    }
    

    cf7f6d4d-02c5-4049-8b50-529f24d32097-image.png
    测试成功,目标达成!如果各位大佬有什么更好的建议或者我有什么代码上的错误,欢迎在评论区一起讨论!

    1 条回复 最后回复
    1
    • SPeakS 离线
      SPeakS 离线
      SPeak d2learn-dev
      编写于 最后由 SPeak 编辑
      #2

      算法复杂度直观通俗理解: 每次比较完成, 被选中的基准值会被放到最终他所在的位置, 所以比较N遍即可完成排序, 而每次比较完后会分裂成2个数组, 总的数组就 $log_2^N$级别

      所以算法复杂度就是 比较的遍数 x 要比较的数组的数量, 即:

      $$
      O(nlog_2^n)
      $$

      注: 文章写完后, 也可以分享到社区群里

      1 条回复 最后回复
      1

      • 登录

      • 没有帐号? 注册

      • 登录或注册以进行搜索。
      d2learn forums Powered by NodeBB
      • 第一个帖子
        最后一个帖子
      0
      • 版块
      • 最新
      • 标签
      • 热门
      • Online Tools
      • 用户
      • 群组