排序算法 --- 二分查找、快排、归并、分治思想、双重最值问题

本文详细介绍了分治思想在排序算法中的应用,包括快速排序、归并排序和二分查找。快速排序通过不断分组确保数组有序;归并排序将两个有序数组合并成一个有序数组;二分查找在有序序列中高效定位元素。此外,还讨论了如何在有序数组中寻找最大值和最小值的分治方法。
摘要由CSDN通过智能技术生成

分治思想:分而治之,把一个复杂的问题按一定的"分解方法",分为等价规模较小的若干部分,逐个解决问题,分别找出每个问题的解

基本步骤:

1.分解过程:把问题分解为两个或者多个问题

2.治理问题:分别解决子问题

3.合并过程:把所有子问题合并起来(递归、递推)

快速排序:分组排序    无限分组,无限把数据分成有序的两组,分到没办法再分组后,整个数组自然有序
           10个
     5个      5个        左边的都小于右边
  2    3     2   3
1  1  1 2  1 1 1 2     
         1  1        1  1

分组过程中保证  左边每个元素比右边每个元素要小

归并排序:有序的两个数组合并成一个有序数组   一开始只负责拆,拆到只有一个元素后,自然就有序了,有序后,把有序的两个数组合并成一个有序数组
          10个
     5个      5个        左边的都小于右边
  2    3     2   3
1  1  1 2  1 1 1 2     
         1   1       1   1
1  1  1  2  1 1 1  2
  2     3      2    3
     5            5
           10

step1:两个有序数组合并成一个有序数组
step2:一个无序数组用归并排序排序成一个有序数组

二分查找:有序序列(二叉树、数组、链表 . . .)中,二分查找的前提是有序,如果无序没办法分而治之

效率较高,每次可以排除掉一半  
在 1 ~ 100 中找 1个数,先找 50

要找的数如果 > 50,取大的一半

要找的数如果 < 50,取小的一半

时间复杂度分析

比如:总共有n个元素,每次查找的区间大小就是n,n / 2,n / 4,,n / 2^k(接下来操作元素的剩余个数),其中k就是循环的次数
由于 n / 2^k 取整后 >= 1,即令 n / 2^k = 1,
可得k = log2n,(是以2为底,n的对数),所以时间复杂度可以表示O() = O(logn)

部分内容参考:"二分查找"算法的时间复杂度

普通数组查找

#include<stdio.h>
int searchData(int array[],int arrayNum,int posData)
{
    for(int i = 0;i < arrayNum;i++ )
    {
        if(array[i] == posData)
            return i;
    }
    return -1;
}

循环方式实现二分查找 

直接找下标为一半的位置 - - - 元素个数 / 2

注意:不能直接 len / 2,如果已经排除前面一半,需要去后面一半去找,不能直接用 len / 2

#include <iostream>
using namespace std;

#define NUM 18
void travel(int* a, int len, bool isAfter = false);

//冒泡排序
void bubble_sort(int* a, int len);

/*
	从arr数组中找值为findData的元素 
	找到返回其下标 没找到返回-1
	len为数组元素个数
*/
int halfFind(int* arr, int len, const int& findData);

int main(){
	int arr[NUM] = { 31, 41, 59, 26, 53, 58, 97, 93, 23, 84, 62, 64,
		33, 83, 27, 95, 2, 88 };

	bubble_sort(arr, NUM);
	travel(arr, NUM, true);

	int n;
	int ret;
	while (1){
		cout << "请输入要找的元素值:";
		cin >> n;
		ret =  halfFind(arr, NUM, n);
		if (-1 == ret){
			cout << "没找到!" << endl;;
		}
		else{
			cout << "找到了,数据:" << arr[ret] << "下标为:" << ret << endl;
		}
	}

	while (1);
	return 0;
}

void travel(int* a, int len, bool isAfter){
	if (isAfter){
		cout << "after  sort:";
	}
	else{
		cout << "before sort:";
	}

	for (int i = 0; i < len; i++)
		cout << a[i] << " ";

	cout << endl;
}

//冒泡排序
void bubble_sort(int* a, int len){
	//n-1次
	for (int i = 0; i < len - 1; i++){
		//比较相邻两个,大的后挪
		for (int j = 0; j < len - i - 1; j++){
			if (a[j] > a[j + 1]){//比较相邻两个  不满足要求
				//交换
				int temp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = temp;
			}
		}
	}
}

int halfFind(int* arr, int len, const int& findData){
	int m;				//中间的下标
	int left=0;			//左边下标
	int right=len-1;	//右边下标
	while (1){
		//把中间下标算出来
		m = left + (right - left) / 2;
		if (findData == arr[m]){//判断是否找到了  
			return m;//找到了 返回
		}
		else{
			//没找到  修改left和right的值  继续循环
			if (findData > arr[m]){//要找数据比中间数据大 
				left = m + 1;//排除掉左边->右边下标保持不变,左边下标修改为m+1
			}
			else{//要找数据比中间数据小 
				right = m - 1;//排除掉右边->左边下标保持不变,右边下标修改为m-1
			}
		}
		if (left > right) break;//找不到 防止死循环 在left==right之后 总会出现left>right的情况
	}
	return -1;
}

/*输出*/
after  sort:2 23 26 27 31 33 41 53 58 59 62 64 83 84 88 93 95 97
请输入要找的元素值:2
找到了,数据:2下标为:0
请输入要找的元素值:97
找到了,数据:97下标为:17
请输入要找的元素值:1
没找到!
请输入要找的元素值:98
没找到!
请输入要找的元素值:42
没找到!
请输入要找的元素值:41
找到了,数据:41下标为:6
请输入要找的元素值:

 递归方式实现二分查找

 循环能实现的递归一定能实现,递归能实现的循环不一定能实现

 循环改为递归:数据的改变 改成 参数的改变,结束循环 改成 return

 在实现代码时,如果循环和递归都可以实现,一般认为循环更快,由于递归存在函数的调用过程

/*
    从arr数组中找值为findData的元素
    找到返回其下标 找不到返回-1
    len为数组元素个数
*/
int half_find(int* arr, int len, const int& findData);
//用于递归的函数
int Half_Find(int* arr, int left, int right, const int& findData); //再加一个函数 在函数中递归 函数的参数 len 改为左边和右边

int half_find(int* arr, int len, const int& findData){
	return Half_Find(arr, 0, len - 1, findData);                   //传入要查找的数组 左 右 要找的数据
}
int Half_Find(int* arr, int left, int right, const int& findData){
	if (left > right) return -1;                                   //找不到 防止死循环
	int m = left + (right - left) / 2;                             //计算中间值
	if (findData == arr[m]){//判断是否找到了  
		return m;//找到了 返回
	}
	//没找到  修改left和right的值  继续循环
	if (findData > arr[m]){//要找数据比中间数据大 
		return Half_Find(arr, m + 1, right, findData); //排除掉左边->改变左边为m+1,右边不变
	}
	else{//要找数据比中间数据小 
		return Half_Find(arr, left, m - 1, findData);  //排除掉右边->改变右边为m-1,左边不变
	}
}

/*输出*/
after  sort:2 23 26 27 31 33 41 53 58 59 62 64 83 84 88 93 95 97
请输入要找的元素值:2
找到了,数据:2下标为:0
请输入要找的元素值:97
找到了,数据:97下标为:17
请输入要找的元素值:1
没找到!
请输入要找的元素值:98
没找到!
请输入要找的元素值:42
没找到!
请输入要找的元素值:41
找到了,数据:41下标为:6
请输入要找的元素值:

两个有序数组合并成一个有序数组

引入一个临时数组,比较两个下标下的值,把小的放入临时数组中,右挪,再去比较. . .直到有一边挪完为止,再把另一边剩下的挪到临时数组中即可

 

#include <iostream>
using namespace std;

#define NUM 18
void travel(int* a, int len, bool isAfter = false);

/*
	left  ~ mid   左边的
	mid+1 ~ right 右边的
*/
void MergeSort(int* a, int left, int mid, int right);

int main() {
#if 1	
	int arr[NUM] = { 31, 41, 59, 26, 53, 58, 97, 93, 23, 84, 62, 64,
		33, 83, 27, 95, 2, 88 };
#else //先写两个有序的数组:前后两个数组都是有序的,中间未必有序-> 合并成一个数组后依然有序
	int arr[NUM] = { 23, 26, 31, 41, 53, 58, 59, 93, 97, 
		2, 27, 33, 62, 64, 83, 84, 88, 95 };
#endif
	
	travel(arr, NUM, true);

	//MergeSort(arr, 0, 0 + (NUM - 1 - 0) / 2, NUM - 1);
	//MergeSort(arr, 0, 8, 17);

	//merge_sort(arr, 0, 17);
	mergeSort(arr, NUM);
	travel(arr, NUM, true);

	while (1);
	return 0;
}

void travel(int* a, int len, bool isAfter){
	if (isAfter){
		cout << "after  sort:";
	}
	else{
		cout << "before sort:";
	}

	for (int i = 0; i < len; i++)
		cout << a[i] << " ";

	cout << endl;
}

/*
left  ~ mid   左边的
mid+1 ~ right 右边的
*/
void MergeSort(int* a, int left, int mid, int right){ //左边往中间挪,中间 + 1 挪到右边为止
	int L = left;    //绿色箭头
	int R = mid + 1; //红色箭头
	int k = 0;       //作为pTemp的下标
	//1 申请临时内存空间
	int* pTemp = new int[right - left + 1];
	//2 一边比较一边把数据从a放到pTemp中
	while (L<=mid && R<=right){                  //要考虑不能超过范围->只要有一个超过了,意味着有一边放完了-> 循环结束
		if (a[L] < a[R])	pTemp[k++] = a[L++]; //如果左边的小,把左边的放入pTemp数组中
		else				pTemp[k++] = a[R++]; //如果右边的小,把右边的放入pTemp数组中
	}
	//3 把剩下的一半放入pTemp中-> 不知道是左边没放完还是右边没放完
#if 0
	while (L <= mid){ pTemp[k++] = a[L++]; }     //左边没放完,把左边的放完
	while (R <= right){ pTemp[k++] = a[R++]; }   //右边没放完,把右边的放完
#else
	if (L <= mid) {
		memcpy(pTemp + k, a + L, sizeof(int)*(mid-L+1));
	}
	else {
		memcpy(pTemp + k, a + R, sizeof(int)*(right-R+1));
	}
#endif
	//4 pTemp中数据覆盖回 a
#if 0
	int j = 0;
	for (int i = left; i <= right; i++) {
		a[i] = pTemp[j++];
	}
#else
	memcpy(a + left, pTemp, sizeof(int)*(right - left + 1)); //元素个数 == 最后一个位置的下标 - 第一个位置的下标 + 1
#endif
	//5 释放
	delete[] pTemp;
}

一个无序数组用归并排序排序成一个有序数组 

//归并排序
void merge_sort(int* a, int left, int right);
//常规写法归并排序
void mergeSort(int* a, int len);

void merge_sort(int* a, int left, int right){
	if (left < right){//如果左边==右边就不用拆了
		int m = left + (right - left) / 2;
		//拆
		merge_sort(a, left, m);		//拆左边
		merge_sort(a, m + 1, right);//拆右边
		//合
		MergeSort(a, left, m, right);
		//travel(a+left, right-left+1);
	}
}
void mergeSort(int* a, int len){
	merge_sort(a, 0, len - 1);
}

/*输出*/

after  sort:31 41 59 26 53 58 97 93 23 84 62 64 33 83 27 95 2 88
before sort:31 41
before sort:31 41 59
before sort:26 53
before sort:26 31 41 53 59
before sort:58 97
before sort:23 93
before sort:23 58 93 97
before sort:23 26 31 41 53 58 59 93 97
before sort:62 84
before sort:62 64 84
before sort:33 83
before sort:33 62 64 83 84
before sort:27 95
before sort:2 88
before sort:2 27 88 95
before sort:2 27 33 62 64 83 84 88 95
before sort:2 23 26 27 31 33 41 53 58 59 62 64 83 84 88 93 95 97
after  sort:2 23 26 27 31 33 41 53 58 59 62 64 83 84 88 93 95 97

 快速排序

快速排序:分组排序    无限分组,在分组的过程中,保证分成的两个组是有序的,左边的每一个元素都比右边的每一个元素要小

组分完后,整个数组就有序了

用两个指针实现左右两边有序,如果规定最左边的数据为临时数据,先挪 r,如果规定最右边的数据为临时数据,先挪 L:让两个指针按图中的方式移动,在移动的过程中调整位置,保证两个指针在同一个位置的时候,左边的每一个元素都比右边的每一个元素小

不采用交换而是采用覆盖的方式,可以提高效率   

如果右指针对应的数字大于31,直接左挪. . .如果小于31,需要把当前值覆盖到左指针所在的位置

如果左指针对应的数字小于31,直接右挪. . .如果大于31,需要把当前值覆盖到右指针所在的位置

到达两个左右指针重合的情况,把 31覆盖回来即可

最后得到:保证了临时数据在中间,并且定位到了中间数据,因为左右指针重合了,比临时数据 31 小的在左边,比临时数据 31 大的在右边

继续针对2~26 和 58~88 把它们看成一个数组,继续进行以上操作,直到整个数组有序

时间复杂度分析:最容易理解最全的快排的最好时间复杂度分析_XIAXIAgo的博客-CSDN博客_快排的时间复杂度分析

#include <iostream>
using namespace std;
#define NUM 18
void travel(int* a, int len, bool isAfter = false);

//分组排序
void quick_sort(int* a, int left, int right);

int main(){
	int arr[NUM] = { 31, 41, 59, 26, 53, 58, 97, 93, 23, 84, 62, 64,
		33, 83, 27, 95, 2, 88 };
	travel(arr, NUM, true);

	quick_sort(arr, 0, 17);

	travel(arr, NUM, true);
	while (1);
	return 0;
}
void travel(int* a, int len, bool isAfter){
	if (isAfter){
		cout << "after  sort:";
	}
	else{
		cout << "before sort:";
	}

	for (int i = 0; i < len; i++)
		cout << a[i] << " ";

	cout << endl;
}

//分组排序
void quick_sort(int* a, int left, int right){
	if (left >= right) return;

	//假设a[left]是中间数据
	int temp = a[left];
	int L = left;	//用来移动的左下标
	int R = right;  //用来移动的右下标

	//循环让L和R并拢
	while (L < R){  //L==R的情况就并拢了
		//先挪R
		while (L < R && a[R] >= temp) R--; //可能在挪的过程中出现L > R的情况 大于的情况 R-- 即可
		a[L] = a[R];                       //停住后 要用右边的覆盖左边的
		//再挪L
		while (L < R && a[L] < temp) L++;
		a[R] = a[L];                       //出现大于或者等于的情况 要用左边的覆盖右边的
	}
	a[L] = temp;                           //覆盖完后 最后把temp覆盖回来

	travel(a, NUM);

	//继续拆 拆到不能拆为止
	quick_sort(a, left, L - 1);           //左边继续拆 左边还是left 右边变成L-1或者R-1 L == R
	quick_sort(a, L + 1, right);          //右边继续拆 L == R
}

/*输出*/

after  sort:31 41 59 26 53 58 97 93 23 84 62 64 33 83 27 95 2 88
before sort:2 27 23 26 31 58 97 93 53 84 62 64 33 83 59 95 41 88    //L和R的值都是4
before sort:2 27 23 26 31 58 97 93 53 84 62 64 33 83 59 95 41 88
before sort:2 26 23 27 31 58 97 93 53 84 62 64 33 83 59 95 41 88
before sort:2 23 26 27 31 58 97 93 53 84 62 64 33 83 59 95 41 88
before sort:2 23 26 27 31 41 33 53 58 84 62 64 93 83 59 95 97 88
before sort:2 23 26 27 31 33 41 53 58 84 62 64 93 83 59 95 97 88
before sort:2 23 26 27 31 33 41 53 58 59 62 64 83 84 93 95 97 88
before sort:2 23 26 27 31 33 41 53 58 59 62 64 83 84 93 95 97 88
before sort:2 23 26 27 31 33 41 53 58 59 62 64 83 84 93 95 97 88
before sort:2 23 26 27 31 33 41 53 58 59 62 64 83 84 93 95 97 88
before sort:2 23 26 27 31 33 41 53 58 59 62 64 83 84 88 93 97 95
before sort:2 23 26 27 31 33 41 53 58 59 62 64 83 84 88 93 95 97
after  sort:2 23 26 27 31 33 41 53 58 59 62 64 83 84 88 93 95 97

 普通方式找最大值和最小值

#include<stdio.h>
//数组 长度 最大值 最小值
void searchMaxMin(int array[],int arrayNum,int* maxIndex,int* minIndex)
{
    //假设第一个元素是最大值也是最小值
    int max = array[0];
    int min = array[0];
    //最大元素和最小元素的下标都等于0
    *maxIndex = 0;
    *minIndex = 0;
    //从第二个开始比较
    for(int i = 1;i < arrayNum;i++ )
    {
        //如果当前元素 > max需要改变最大值与最大值的下标
        if(array[i] > max)
        {
            max = array[i];
            *maxIndex = i;
        }
        //同理找最小值
        if(array[i] < min)
        {
            min = array[i];
            *minIndex = i;
        }
    }
}
int main()
{
    int array[10] = {4,5,7,8,9,0,1,6,2,3};
    int maxIndex = -1;
    int minIndex = -1;
    searchMaxMin(array,10,&maxIndex,&minIndex);
    printf("max = %d\nmin = %d\n",maxIndex,minIndex);
    return 0;
}    

/*输出*/

max = 4
min = 5

分而治之的方式找最大值和最小值

每两个元素比较出最大值和最小值,把比较出的最大值、最小值和前一组的最大值、最小值去比较

会出现元素总个数为奇数的情况需要处理

void searchMaxMin2(int array[], int arrayNum, int* maxIndex, int* minIndex) 
{
	//1.只有一个元素既是最大值也是最小值
	if (arrayNum == 1) 
	{
		*maxIndex = *minIndex = 0;
	}
	//2.先解决奇偶问题-> 如果是奇数下一组比较从下标1开始 如果是偶数下一组比较从下标2开始
    //奇数下一组从1开始
	int n = 1;
    //奇数
	if (arrayNum % 2 == 1) 
	{
        //只有一个元素的一组
		*maxIndex = *minIndex = 0;
	}
    //偶数
	else 
	{
        //两个元素一组-> 直接把第0个元素和第1个元素拿出来比较找出最大值和最小值
		if (array[0] > array[1]) 
		{
			*maxIndex = 0;
			*minIndex = 1;
		}
		else 
		{
			*maxIndex = 1;
			*minIndex = 0;
		}
        //偶数下一组从2开始
		n = 2;
	}
    //每次比较一组(两个)元素
	for (int i = n; i < arrayNum; i += 2) 
	{
        //一组的左边和右边比较出当前最大值和最小值
		if (array[i] > array[i + 1])	            //下一组找出最小的和最大的
		{
			if (array[i] > array[*maxIndex])        //把当前组的最大值和上一组的最大值比
			{
				*maxIndex = i;                      //如果比上一组的最大值大就改变最大值
			}
			if (array[i + 1] < array[*minIndex])    //把当前组的最小值和上一组的最小值比 
			{
				*minIndex = i + 1;                  //同理改变最小值
			}
		}
		else 
		{
			if (array[i + 1] > array[*maxIndex]) 
			{
				*maxIndex = i + 1;
			}
			if (array[i] < array[*minIndex]) 
			{
				*minIndex = i;
			}
		}
	}
}
  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qiuqiuyaq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值