快速排序的三路划分方法和归并排序的递归和非递归实现

news/2024/7/5 5:21:20

目录

快速排序的三路划分方法 

 归并排序的递归实现

 归并排序的非递归实现

快速排序的三路划分方法 

首先快排的时间复杂度为O(N*logN),空间复杂度O(logN),不稳定。

三路划分:将数据分为三份;可以提高当数据中出现多个重复数字时的效率。

 如上图所示,小于key的值移动到左边,等于key的值放在中间,大于key的值移动到右边。

具体操作如下图所示:

核心思想:

  1. 比key小的值移动到左边 
  2. 与key相等的值,往后移动 
  3. 比key大的值移动到右边 
  4. 与key相等的值移动到了中间

 对于key的取值,我们选用随机取值的方法取值。因为如果使用三数取中的话,面对特殊用例的时候,可能大量区间选key让你选到比较小或者比较大的,导致性能下降。

代码:

#include <stdio.h>      /* printf, scanf, puts, NULL */
#include <stdlib.h>     /* srand, rand */
#include <time.h>       /* time */
#include <sting.h>

void Swap(int *p1,int *p2)
{
    int tmp=*p1;
    *p1=*p2;
    *p2=tmp;
}

void InsertSort(int* a, int n)
{
	for (int i = 0; i < n-1; ++i)
	{
		int end = i;
		int tmp = a[end + 1];
		while (end>=0)
		{
			if (tmp<a[end])
			{
				a[end + 1] = a[end];
				--end;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}
}

int GetMidIndex(int* a, int begin, int end)
{
	int mid = begin+rand()%(end-begin);
	if (a[begin] < a[mid])
	{
		if (a[begin] > a[end])
		{
			return begin;
		}
		else if (a[mid] < a[end])
		{
			return mid;
		}
		else
		{
			return end;
		}
	}
	else//a[begin]>a[mid]
	{
		if (a[mid] > a[end])
		{
			return mid;
		}
		else if (a[begin] < a[end])
		{
			return begin;
		}
		else
		{
			return end;
		}
	}
}


void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
	if ((end - begin + 1) < 15)
	{
		//小区间用直接插入代替,减少递归调用次数
		InsertSort(a + begin, end-begin+1);
	}
	else
	{
		int mid =GetMidIndex(a,begin,end);
		Swap(&a[begin],&a[mid]);

		int left=begin,right=end;
		int key=a[begin];
		int cur=begin+1;

		while(cur<=right)
		{
			if(a[cur]<key)
			{
				Swap(&a[cur],&a[left]);
				++cur;
				++left;
			}
			else if(a[cur]>key)
			{
				Swap(&a[cur],&a[right]);
				--right;
			}
			else
			{
				cur++;
			}
		}
		//[begin,left-1][left,right] [right+1,end]左边小右边大,中间的不处理
		QuickSort(a, begin, left-1);
		QuickSort(a, right+1, end);
	}
}

void PrintArray(int* a, int n)
{
	for (int i = 0; i < n; ++i)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

int main()
{
    int a[]={2,1,2,7,2,2,4,5,2,8}; 
    QuickSort(a,0,sizeof(a)\sizeof(int)-1);
    PrintArray(a, sizeof(a) / sizeof(int));
    return 0;
}

 运行结果:

 归并排序的递归实现

归并排序的时间复杂度为O(N*logN),空间复杂度O(N),稳定。 

以升序为例,对于无序的数据,不断将数据划分为两个子区间,当每个子区间划分为只有一个数的时候认为有序,然后对每个子区间进行归并排序

对于两个有序的子区间,依次比较,取小的尾插

当其中一个子区间尾插完,剩下的依次尾插到数组里;具体图下图所示。

代码:

void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin >= end)
	{
		return;
	}
	int mid = (begin + end) / 2;
	_MergeSort(a, begin, mid, tmp);      //递归让子区间有序
	_MergeSort(a, mid + 1, end, tmp);

	//归并 [begin,mid][mid+1,end]
	int i = begin;
	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] <= a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
	}

	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}
	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
}

// 归并排序递归实现
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * (n));
	if(tmp==NULL)
	{
		perror("malloc failed");
		exit(-1);
	}
	
	_MergeSort(a, 0, n - 1, tmp);

	free(tmp);
	tmp = NULL;
}

void PrintArray(int* a, int n)
{
	for (int i = 0; i < n; ++i)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

int main()
{
    int a[]={10,6,7,1,3,9,4,2};
    MergeSort(a,sizeof(a)\sizeof(int));
    PrintArray(a, sizeof(a)\sizeof(int));
    return 0;
}

运行结果截图

 归并排序的非递归实现

思想和递归的思想差不多,定义一个rangeN来控制数据跳动的步长,达到将数据分为子区间的效果。如下图所示。(begin1和end1作为左子区间的边界,begin2和end2作为右子区间的边界。)

但是控制不好会越界,越界访问需要注意的情况有

  1. end1 会越界
  2. begin2和end2越界
  3. end2越界

 对于越界的边界,我们需要重新修正。

#define  _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>

// 归并排序非递归实现
void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}

	int rangeN = 1;
	while (rangeN < n)
	{
		for (int i = 0; i < n; i += 2 * rangeN)
		{
			int begin1 = i, end1 = i + rangeN - 1;
			int begin2 = i + rangeN, end2 = i + 2 * rangeN - 1;
			int j = i;

			// end1 begin2 end2 越界
			// 修正区间  ->拷贝数据 归并完了整体拷贝 or 归并每组拷贝
			if (end1 >= n)
			{
				end1 = n - 1;
				// 不存在区间
				begin2 = n;
				end2 = n - 1;
			}
			else if (begin2 >= n)
			{
				// 不存在区间
				begin2 = n;
				end2 = n - 1;
			}
			else if (end2 >= n)
			{
				end2 = n - 1;
			}

			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}

			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}

			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}

		}
		// 整体归并完了再拷贝
		memcpy(a, tmp, sizeof(int) * (n));
		rangeN *= 2;
	}
	free(tmp);
	tmp = NULL;
}

void PrintArray(int* a, int n)
{
	for (int i = 0; i < n; ++i)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

int main()
{
	int a[] = { 10,6,7,1,3,9,4,2 };
	MergeSortNonR(a, sizeof(a)/sizeof(int));
	PrintArray(a, sizeof(a)/sizeof(int));
	return 0;
}

运行结果截图:


http://lihuaxi.xjx100.cn/news/1305215.html

相关文章

有没有免费提取音频的软件,分享几个给大家!

在日常生活中&#xff0c;我们经常遇到需要从视频中提取音频的情况&#xff0c;无论是为了制作音频片段、录制语音笔记还是进行后期编辑。本文将介绍三种免费提取音频的方法&#xff0c;分别是记灵在线工具、PR&#xff08;Adobe Premiere Pro&#xff09;和剪映。通过这些方法…

【新版系统架构】第九章-软件可靠性基础知识

软考-系统架构设计师知识点提炼-系统架构设计师教程&#xff08;第2版&#xff09; 第一章-绪论第二章-计算机系统基础知识&#xff08;一&#xff09;第二章-计算机系统基础知识&#xff08;二&#xff09;第三章-信息系统基础知识第四章-信息安全技术基础知识第五章-软件工程…

基于Springboot+mybatis+mysql+vue实现企业注册模块功能

基于Springbootmybatismysqlvue实现企业注册模块功能 一、系统介绍二、功能展示1.主页面2.注册成功 三、数据库四、代码展示四、其他系统实现五、获取源码 一、系统介绍 该系统实现简单的企业信息注册&#xff0c;保存后&#xff0c;提示注册成功。 运行环境&#xff1a;idea…

哈希表--day6--总结篇

文章目录 数组作为哈希表set作为哈希表map作为哈希表 一般来说哈希表都是用来快速判断一个元素是否出现集合里。 对于哈希表&#xff0c;要知道哈希函数和哈希碰撞在哈希表中的作用. 哈希函数是把传入的key映射到符号表的索引上。 哈希碰撞处理有多个key映射到相同索引上时的…

unity制作游戏,点击鼠标左键,展示屏幕震动效果

在Unity中实现点击鼠标左键展示屏幕震动效果可以通过以下步骤进行&#xff1a; 创建一个新的C#脚本&#xff0c;例如"ScreenShake.cs"&#xff0c;并将其附加到想要添加屏幕震动效果的游戏对象上。 在脚本中定义一个变量来控制震动的幅度&#xff0c;例如public flo…

运输层概述、端口号、复用与分用

1.运输层概述、端口号、复用与分用 笔记来源&#xff1a; 湖科大教书匠&#xff1a;运输层概述 湖科大教书匠&#xff1a;运输层端口号、复用与分用的概念 声明&#xff1a;该学习笔记来自湖科大教书匠&#xff0c;笔记仅做学习参考 1.1 运输层概述 计算机网络体系结构中的物…

【Android】组件安全之Activity

前言 前文系统的总结了组件会有什么安全问题&#xff0c;本文详细的从不同的组件切入&#xff0c;深入的解析组件的实现方式。组件安全https://blog.csdn.net/xiru9972/article/details/123537641?ops_request_misc&request_id7a89a72fdd464cee90c20b2b88549c11&biz_…

Springboot MongoDB封装通用Servcie

上篇文章讲到了如何基于MongoTemplate封装通用Repository&#xff0c;只是解决了数据存储层的代码冗余&#xff0c;但是没有解决业务层的冗余&#xff0c;为了解决业务层的代码冗余&#xff0c;封装一个类似Mybatis plus的通用Service&#xff0c;相信使用过Mybstis plus的同学…