《算法笔记》学习笔记(11):two pointers

two pointers更倾向于是一种编程技巧,而非一种算法。

从一个例子引入:一个递增的正整数序列,查找两个数a和b,设的他们的和为m。
若直接使用二重循环枚举,显然时间复杂度为O(n2),同时也会产生大量无效枚举,效率低下。
若采用two pointers的方法,令下标i初值为0,j初值为n-1,通过移动i,j来寻找,直到i≥j成立。移动过程中有一下三种情况:
1.若a[i]+a[j]==m,因为序列递增,则i++,j–
2.若a[i]+a[j]>m,j–
3.若a[i]+a[j]>m,i++
由此可以得到代码

int main()
{
	int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int i = 0, j = 9;
	int m = 8;
	while (i < j)
	{
		if (a[i] + a[j] == m)
		{
			printf("%d %d\n", i, j);
			i++;
			j--;
		}
		else if (a[i] + a[j] < m)
		{
			i++;
		}
		else
		{
			j--;
		}
	}
	return 0;
}

运行结果
在这里插入图片描述
序列合并问题
假设有两个递增序列A与B,要求将他们合并成一个递增序列C。同样的可以设置两个下标i和j,分别指向A的第一个元素和B的第一个元素,然后根据A[i]与B[j]的大小决定哪一个放入序列C
同理可写出代码

int merge(int A[], int B[], int C[], int n,int m)
{
	int i = 0, j = 0, index = 0;//i指向a[0],j指向b[0]
	while (i < n && j < m)
	{
		if (A[i] <= B[j])//如果A[i] <= B[j]
		{
			C[index++] = A[i++];//将A[i]加入序列,i++
		}
		else//如果A[i]>B[j]
		{
			C[index++] = B[j++];//将B[j]加入序列,j++
		}
	}
	while (i < n)C[index++] = A[i++];
	while (j < m)C[index++] = B[j++];//处理剩下的数
	return index;
}

int main()
{
	int a[] = { 1,3,5,7,9 };
	int b[] = { 2,4,6,8,10,12 };
	int c[100];
	int asize = sizeof(a) / sizeof(a[0]);
	int bsize = sizeof(b) / sizeof(b[0]);
	int csize=merge(a, b, c, asize, bsize);
	printf("合并后的数组:\n");
	for (int i = 0; i < csize; i++)
	{
		printf("%d ", c[i]);
	}
	printf("\n");
	printf("合并后的长度为:%d\n", csize);
	return 0;
}

运行结果
在这里插入图片描述
归并排序
归并排序是基于“归并”思想的排序方法。2路归并排序的原理是,将序列归并为⌈n/2⌉ 个组,组内单独排序,然后将这些组两两归并,生成⌈n/4⌉个组,在单独排序,以此类推。时间复杂度为O(nlogn)
1.递归实现
反复将当前区间[left,right]分为两半,对两个子区间[left,mid]和[mid+1,right]归并排序,然后将两个已经有序的子区间合并为有序序列即可。代码如下:

//2路归并排序递归实现
const int maxn = 100;
//将数组A的[L1,R1],[L2,R2]合并为有序区间(此处L2即为R1+1)
void merge(int A[], int L1, int R1, int L2,int R2)
{
	int i = L1, j = L2;//i指向A[L1],j指向A[L2]
	int temp[maxn], index = 0;
	while (i <=R1 && j <= R2)
	{
		if (A[i] <= A[j])//如果A[i] <= B[j]
		{
			temp[index++] = A[i++];//将A[i]加入序列,i++
		}
		else//如果A[i]>B[j]
		{
			temp[index++] = A[j++];//将A[j]加入序列,j++
		}
	}
	while (i <=R1)temp[index++] = A[i++];
	while (j <=R2)temp[index++] = A[j++];//处理剩下的数
	for (int i = 0; i < index; i++)
	{
		A[L1 + i] = temp[i];//重新赋值给A[]
	}
}

void mergeSort(int A[], int left, int right)
{
	if (left < right)
	{
		int mid = (left + right) / 2;
		mergeSort(A, left, mid);
		mergeSort(A, mid + 1, right);
		merge(A, left, mid, mid + 1, right);
	}
}

int main()
{
	int a[] = { 66,12,33,57,64,27,18 };
	mergeSort(a, 0, 6);
	for (int i = 0; i < 7; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
	return 0;
}

运行结果在这里插入图片描述

2.非递归实现
令步长step初值为2,然后将数组中每step个元素作为一组,将其内部进行排序,再令step乘以2,重复上面的操作,直到step/2超过元素个数n,代码如下:

const int maxn = 100;
//将数组A的[L1,R1],[L2,R2]合并为有序区间(此处L2即为R1+1)
void merge(int A[], int L1, int R1, int L2,int R2)
{
	int i = L1, j = L2;//i指向A[L1],j指向A[L2]
	int temp[maxn], index = 0;
	while (i <=R1 && j <= R2)
	{
		if (A[i] <= A[j])//如果A[i] <= B[j]
		{
			temp[index++] = A[i++];//将A[i]加入序列,i++
		}
		else//如果A[i]>B[j]
		{
			temp[index++] = A[j++];//将A[j]加入序列,j++
		}
	}
	while (i <=R1)temp[index++] = A[i++];
	while (j <=R2)temp[index++] = A[j++];//处理剩下的数
	for (int i = 0; i < index; i++)
	{
		A[L1 + i] = temp[i];//重新赋值给A[]
	}
}
int min(int a, int b)
{
	return a < b ? a : b;
}
//2路归并排序非递归实现
void mergeSort(int A[],int n)
{
	for (int step = 2; step / 2 < n; step *= 2)//step为组内元素个数,n为总元素个数
	{//每step个元素为一组,组内前step/2和后step/2个元素合并
		for (int i = 0; i < n; i += step)
		{
			int mid = i + step / 2 - 1;
			if(mid+1<n)
			{
				merge(A, i, mid, mid + 1, min(i + step - 1, n-1));
			}
		}
	}
}

int main()
{
	int a[] = { 66,12,33,57,64,27,18 };
	mergeSort(a, 7);
	for (int i = 0; i < 7; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
	return 0;
}

运行结果同上

快速排序
快排的原理不再赘述

int Partition(int A[], int left, int right)
{
	int temp = A[left];//将A[left]存入临时变量temp
	while (left < right)//left与right不相遇
	{
		while (left<right && A[right]>temp)right--;//不断左移right直到找到一个小于选定值的值
		A[left] = A[right];//将A[right]移到A[left]
		while (left < right && A[left] <= temp)left++;//不断右移left直到找到一个大于等于选定值的值
		A[right] = A[left];//将A[left]移到A[right]
	}
	A[left] = temp;//temp归位
	return left;//返回相遇的下标
}
void quickSort(int A[], int left, int right)
{
	if (left < right)
	{
		int pos = Partition(A, left, right);
		quickSort(A, left, pos - 1);
		quickSort(A, pos + 1, right);
	}
}
int main()
{
	int a[] = { 66,12,33,57,64,27,18 };
	quickSort(a, 0, 6);
	for (int i = 0; i < 7; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
	return 0;
}

随机快排
上面版本的快排在选取主元的时候,每次都选取最右边的元素。当序列为有序时,会发现划分出来的两个子序列一个里面没有元素,而另一个则只比原来少一个元素。为了避免这种情况,引入一个随机化量来破坏这种有序状态。

在随机化的快排里面,选取a[left…right]中的随机一个元素作为主元,然后再进行划分,就可以得到一个平衡的划分。

实现起来其实只需要对上面的代码做小小的修改就可以了。

int randPartition(int A[], int left, int right)
{
	int p = (round(1.0 * rand() / RAND_MAX * (right - left) + left));
	swap(A[p], A[left]);
	int temp = A[left];//将A[left]存入临时变量temp
	while (left < right)//left与right不相遇
	{
		while (left<right && A[right]>temp)right--;//不断左移right直到找到一个小于选定值的值
		A[left] = A[right];//将A[right]移到A[left]
		while (left < right && A[left] <= temp)left++;//不断右移left直到找到一个大于等于选定值的值
		A[right] = A[left];//将A[left]移到A[right]
	}
	A[left] = temp;//temp归位
	return left;//返回相遇的下标
}
void quickSort(int A[], int left, int right)
{
	if (left < right)
	{
		int pos = randPartition(A, left, right);
		quickSort(A, left, pos - 1);
		quickSort(A, pos + 1, right);
	}
}
int main()
{
	int a[] = { 66,12,33,57,64,27,18 };
	quickSort(a, 0, 6);
	for (int i = 0; i < 7; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值