算法与数据结构(二)

本文介绍了使用二分法寻找数组最大值的高效算法,以及归并排序的过程,包括如何合并数组和计算小和。此外,还讨论了数组的逆序对问题和荷兰国旗问题,最后提到了快速排序的随机数版本。
摘要由CSDN通过智能技术生成

力扣刷题

一、在一个无序数组找到该数组中的最大值,要求时间复杂度低最快方法是用二分法:时间复杂度为 O ( l o g N ) O(logN) O(logN)

int process(int arr[], int L, int R)
{ 	//如果最左边的下标等于最右边的下标,则证明只有一个数,则返回
	if (L == R)
	{
		return arr[L];
	}
	
	int mid = L + ((R - L) >> 1);
	int leftMax = process(arr, L, mid);
	int rightMax = process(arr, mid + 1, R);
	return max(leftMax, rightMax);
};

void test12()
{
	int arr[] = { 2, 2, 3, 1, 7, 1, 5, 6 };
	int a = process(arr, 0, sizeof(arr) / sizeof(arr[0]) - 1);
	cout << a << endl;
}

对以上递归函数的内容进行解释:

int mid = L + ((R - L) >> 1);

这句是对整个数组取中间值的操作。我们在数学上取中间值的操作为(L+R)/2,但是这样操作起来会出现越界的情况,所以我们可以将上述的操作改为L + (R-L)/2。我们也可以将/2改写为右移一位,即>>1。这样会优化代码的计算效率。

int leftMax = process(arr, L, mid);

这句是取二分法之后左边的最大值,再进行一个二分操作。操作完成后进行递归,具体的递归细节请自行了解。
上述的时间复杂度符合master公式:
T ( N ) = a ∗ T ( N / b ) + O ( N d ) T(N) = a * T(N/b)+ O(N^d) T(N)=aTN/b+O(Nd)
我们只考虑一层递归的操作,N为数组的总量,因为是二分法,所以b = 2,又因为一层递归只需要求出左右两侧的最大值,所 a = 2,而上述代码中除了递归之外并没有其余的操作,所以 O ( N d ) O(N^d) O(Nd) 为1。综上,上述的时间复杂度为 2 ∗ T ( N / 2 ) + O ( N 0 ) 2 * T(N/2)+ O(N^0) 2TN/2+O(N0)
令master公式还有如下定律:
(1) l o g ( b , a ) > d log(b,a)>d log(b,a)>d 则其时间复杂度为 O ( N l o g ( b , a ) ) O(N^log(b,a)) O(Nlog(b,a))
(2) l o g ( b , a ) = = d log(b,a)==d log(b,a)==d 则其时间复杂度为 O ( N d ∗ l o g N ) O(N^d*logN) O(NdlogN)
(2) l o g ( b , a ) < d log(b,a)<d log(b,a)<d 则其时间复杂度为 O ( N d ) O(N^d) O(Nd)

二、归并排序

void merge1(int* arr, int L, int M, int R)
{
	int i = 0; //定义p的初始指针位置
	int *p = new int[R - L + 1]; //定义一个临时的数组

	int p1 = L; //左半部分的下标
	int p2 = M + 1; //右半部分的下标
	while (p1 <= M && p2 <= R)
	{
		p[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
	}
	while (p1 <= M)
	{
		p[i++] = arr[p1++];
	}

	while (p2 <= R)
	{
		p[i++] = arr[p2++];
	}


	int k = 0;  //从小标为 0 开始传送
	for (int i = L; i <= R; i++)  //将 b 数组的值传递给数组 a
	{
		arr[i] = p[k++];
	}

	delete[]p;

}


int process1(int *arr, int L, int R)
{
	if (L == R)
	{
		return arr[L];
	}
	int mid = L + ((R - L) >> 1);
	process1(arr, L, mid);
	process1(arr, mid + 1, R);
	merge1(arr, L, mid, R);
};



void test13()
{
	int arr[] = { 0, 3, 4, 1, 2, 3 };

	process1(arr, 0, (sizeof(arr) / sizeof(arr[0])) - 1);

	for (int i = 0; i < (sizeof(arr) / sizeof(arr[0])); i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;

}

原理图如下:
在这里插入图片描述

二、求数组的小和

在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。
例子:[1,3,4,2,5],1左边比1小的数,没有;3左边比3小的数为1;4左边比4小的数为1、3;2左边比2小的数为1;5左边比5小的数为1、3、4、2;所以小和为1+1+3+1+1+3+4+2=16
思路:
在本问题中,我们可以反向来求解过程,也就是反向看,例如:1右边有几个数比1大,则会小和会包括几个1,3右边有几个数比3小,则小和会包括几个3,以此类推。这一过程也可以抽象为一个归并过程。
c++代码如下:

#include<iostream>
using namespace std;

int merge2(int *arr, int L, int M, int R)
{
	int *p = new int[R - L + 1]; //申请一个临时数组
	int i = 0;
	int p1 = L;  //创建左半部分的指针
	int p2 = M + 1; //创建右半部分的指针
	int res = 0; //求小和暂存变量
	//判断两个数组下标是否越界,如果越界则跳出循环
	while (p1 <= M && p2 <= R)
	{
		//通过排序的动作来实现数组小和求解,(R - p2 + 1)求的是右组有多少个比p1小的数字。
		res += arr[p1] < arr[p2] ? (R - p2 + 1) * arr[p1] : 0;
		p[i++] = arr[p1] < arr [p2] ? arr[p1++] : arr[p2++];
	} 

	//如果一边数组没有越界,则进入循环
	while (p1 <= M)
	{
		p[i++] = arr[p1++];
	}

	while (p2 <= R)
	{
		p[i++] = arr[p2++];
	}

	int k = 0; //临时变量p应该从0开始

	for (int i = L; i <= R; i++)
	{
		arr[i] = p[k++];
	}
	
	delete[]p;
	return res;
}


int process2(int *arr, int L, int R)
{
	//如果数组只存在一个数,则抛出
	if (L == R)
	{
		return 0;
	}

	int mid = L + ((R - L) >> 1); //取中间值下标
	process2(arr, L, mid); //递归数组左半部分
	process2(arr, mid + 1, R); //递归数组右半部分
	merge2(arr, L, mid, R);  //开始归并
	return process2(arr, L, mid) + process2(arr, mid + 1, R) + merge2(arr, L, mid, R);

}

void test15()
{
	int arr[] = { 1, 3, 4, 2, 5 };

	int a = process2(arr, 0, (sizeof(arr) / sizeof(arr[0])) - 1);

	for (int i = 0; i < (sizeof(arr) / sizeof(arr[0])); i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
	cout << a << endl;

}

int main() {

	test15();
	system("pause");
	return 0;
}

三、求数组的逆序对

逆序对问题,在一个数组中,左边的数如果比右边的数大,则这两个数构成一个逆序对,请打印所有逆序对。例子:[1,3,4,2,5],1左边比1小的数,没有;3左边比3小的数为1则为3,1;4左边比4小的数为1、3则为4,1;4,3以此类推。与上一个操作方法类似,这里就不在赘述了。

四、荷兰国旗问题

问题一:
给定一个数组arr,和一个数num,请把小于等于num的数放在数组的左边,大于num的数放在数组的右边。要求额外空间复杂度0(1),时间复杂度O(N)。
思路:
1、首先确定小于等于num的边界值,这里我们从数组第一个元素之前为界开始划定。
2、比较数组第一个元素与num的大小,如果小于等于num则与边界右边的数值做交换,对于第一个元素则是自己与自己交换(因为第一个数值是边界范围右侧的第一个数值),然后将指针移动到下一个位置,最小边界移动到第一个元素处。反之则最小边界不移动。以此类推。
c++代码如下:

void he(int* arr, int l, int n)
{

	//如果数组为空或者只有一个数组,则返回
	if (arr == NULL || n == 1)
	{
		return;
	}

	//最小边界的指针
	int k= - 1;
	for (int i = 0; i < l; i++)
	{
		cout << k << endl;
		//如果当前指针的数小于等于我们指定的分界数,则与小于等于区域右侧位置第一个数交换,并且当前指针右移
		if (arr[i]<=n)
		{
			int temp = arr[i];
			arr[i] = arr[k+1];
			arr[k + 1] = temp;
			k++;
		}
	}
	//排序结果输出
	for (int i = 0; i < l; i++)
	{
		cout << arr[i] << "  ";
	}

}

问题二:
给定一个数组arr,和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边。要求额外空间复杂度0(1),时间复杂度O(N)。
思路:
1、首先确定小于num的边界值(第一个元素之前),大于num的边界值(最后一个元素之后)。
2、比较数组第一个元素与num的大小,如果小于num则与小于边界右边的数值做交换,对于第一个元素则是自己与自己交换(因为第一个数值是边界范围右侧的第一个数值),然后将指针移动到下一个位置,最小边界移动到第一个元素处。如果当前数值与num相同,则直接将当前指针移动到下一个。如果当前数值大于num,则与大于的边界左侧值进行互换,边界值左移,指针不动。以此类推。

void he2(int* arr, int l, int n)
{
	
	//如果数组为空或者只有一个数组,则返回
	if (arr == NULL || n == 1)
	{
		return;
	}
	
	//设定小于区域的右边界
	int s = -1;

	//设定大于区域的左边界
	int b = l;


	//开始循环
	int i = 0;
	while (i != b)
	{
		//如果当前元素小于num,则与小于等于区域的右边界下一个数值交换,小于等于区域右移,指针右移
		if (arr[i] < n)
		{
			int temp = arr[i];
			arr[i] = arr[s + 1];
			arr[s + 1] = temp;
			s++;
			i++;
		}

		//如果当前元素大于num,则与大于等于区域的左边界下一个数值交换,大于等于区域左移,指针原地不变
		else if (arr[i] > n)
		{
			int temp1 = arr[i];
			arr[i] = arr[b - 1];
			arr[b - 1] = temp1;
			b--;
		}

		//如果当前元素等于num,则当前指针右移
		else
		{
			i++;
		}	
	}

	//排序结果输出
	for (int i = 0; i < l; i++)
	{
		cout << arr[i] << "  ";
	}
}

五、快速排序随机数版本

快速排序随机数版本过程如下图所示:
在这里插入图片描述

void swap(vector<int>&arr, int i, int j)
{
	int temp = arr[i];
	arr[i] = arr[j];
	arr[j] = temp;
}
vector<int> partition(vector<int> &arr, int l, int r)
{

	int less = l - 1;//less为小于区边界,也就是等于区的开头
	int more = r;//more为大于区边界,等于区的末尾
	while (l < more)
	{
		if (arr[l] < arr[r])//当前数小于划分值
		{
			swap(arr, ++less, l++);//交换当前数与小于区边界
		}
		else if (arr[l] > arr[r])//当前数大于划分值
			swap(arr, l, --more);//交换当前数和大于区边界
		else
			++l;//当前数与划分值相等,啥也不干,往下看下一个数。
	}
	swap(arr, more, r);//当l与more遇上了,则把划分值和大于区的边界交换回来
	vector<int>p{ less + 1,more };//返回等于区的边界
	return p;
}

void quickSort(vector<int>&arr, int l, int r)
{
	if (l < r)
	{
		srand((int)time(NULL));
		swap(arr, r, l + rand() % (r - l + 1));//先随机选这一个数,与末尾数字交换
		vector<int>help = partition(arr, l, r);
		quickSort(arr, l, help[0] - 1);
		quickSort(arr, help[1] + 1, r);
	}

}
void quickSort(vector<int>&a)
{
	if (a.size() < 2)
		return;
	quickSort(a, 0, a.size() - 1);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值