【数据结构与算法】学习笔记-《算法笔记》-13【two pointers】

引例

给定一个递增的正整数序列和一个正整数M,求序列中的两个不同位置的数a和b,使得它们的和恰好为M,输出所有满足条件的方案。

二重枚举的复杂度为O(N2)
two pointers 将利用有序序列的枚举特性来有效降低复杂度。它对本题的算法过程如下:

在这里插入图片描述

	while (i < j)
	{
		if (a[i] + a[j] == m)
		{
			printf("%d%d", i, j);
			i++;
			j--;
		}
		else if (a[i] + a[j] < m)
		{
			i++;
		}
		else
			j--;
	}
}

其i\j的操作次数最多为n次,时间复杂度为O(n)

序列合并问题

假设有两个递增序列A和B,要求将它们合并为一个递增序列C
在这里插入图片描述

int merge(int a[], int b[], int c[], int n, int m)
{
	int i = 0, j = 0, index = 0;
	while (i < n&&j < m)
	{
		if (a[i] <= b[j])
		{
			c[index++] = a[i++];
		}
		else
		{
			c[index++] = b[j++];
		}
	}
	while (i < n)	c[index++] = a[i++];
	while (j < m)	c[index++] = b[j++];//别忘记输入剩余的元素!
	return index;//返回序列c的长度
}

two pointers最原始的含义是针对引例而言的,广义上是利用问题本身与序列的特性,使用两个下标i\j对序列进行同向或反向扫描,以较低复杂度解决问题。

归并排序(2-路归并排序)
在这里插入图片描述
用递归和非递归两种方法实现

  1. 递归
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;
	int temp[maxn], index = 0;
	while (i <= R1 && j <= R2)
	{
		if (A[i] <= A[j])
			temp[index++] = A[i++];
		else
			temp[index++] = A[j++];
	}
	while (i <= R1)	temp[index++] = A[i++];
	while (i <= R2)	temp[index++] = A[j++];
	for (i = 0; i < index; i++)
	{
		A[L1 + i] = temp[i];		//将合并后的序列赋值返回数组A
	}
}
//将array数组当前区间[left,right]进行归并排序
void mergeSort(int A[], int left, int right)
{
	if (left < right)
	{
		int mid = (left + right) / 2;//取[left,right]的中点
		mergeSort(A, left, mid);	//递归,将左子区间[left,mid]归并排序
		mergeSort(A, mid + 1, right);//递归,将右子区间[mid+1,right]归并排序
		merge(A, left, mid, mid + 1, right);//将左子区间和右子区间合并
	}
}
  1. 非递归
void mergeSort(int A[])
{
	//step为组内元素个数,step/2为左子区间元素个数,注意等号可以不取
	for (int step = 2; step / 2 <= n; step *= 2)
	{
		//每step个元素一组,组内前step/2和后step/2个元素进行合并
		for (int i = 1; i <= n; i += step)
		{
			//对每一组
			int mid = i + step / 2 - 1;//左子区间元素个数为step/2
			if (mid + 1 <= n)//右子区间存在元素则合并
			{
				//左子区间为[i,mid                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                ],右子区间为[mid+1,min(i+step-1,n)]
				merge(A, i, mid, mid + 1, min(i + step - 1, n));
			}
		}
	}
}
//或者
void mergeSort(int A[])
{
	//step为组内元素个数,step/2为左子区间元素个数,注意等号可以不取
	for (int step = 2; step / 2 <= n; step *= 2)
	{
		//每step个元素一组,组内[i,min(i+step,n+1)]进行排序
		for (int i = 1; i < n; i += step)
		{
			sort(A + i, A + min(i + step, n + 1));
		}
		//此处可以输出归并排序的某一趟结束的序列
	}
}

快速排序算法
它是排序算法中 平均时间复杂度为O(nlogn) 的一种算法

//对区间[left,right]进行划分
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放到left与right相遇的地方
	return left;	//返回相遇的下标
}

//快速排序,left与right初值为序列首位下标(如1和n)
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);//对右子区间递归进行快速排序
	}
}

但是当序列中元素接近有序时,会达到最坏时间复杂度O(n2),解决问题的办法有随机选择主元
链接–>rand函数产生随机数
具体为: 生成随机数p,然后以A[p]作为主元来进行划分,quickSort函数不需要进行任何改变,randpartition函数只要在最前面加上两句话即可

//对区间[left,right]进行划分
int partition(int A[], int left, int right)
{
	//生成[left.right]内的随机数p
	int p = (round(1.0*rand() / RAND_MAX * (right - left) + left);
	swap(A[p], A[left]);
	//以下为原先partition函数的划分过程,不需要进行改变
	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放到left与right相遇的地方
	return left;	//返回相遇的下标
}
习题:

基础排序III:归并排序

题目描述
归并排序是一个时间复杂度为O(nlogn)的算法,对于大量数据远远优于冒泡排序与插入排序。
这是一道排序练习题,数据量较大,请使用归并排序完成。
输入
第一行一个数字n,代表输入的组数
其后每组第一行输入一个数字m,代表待排序数字的个数
其后m行每行一个数据,大小在1~100000之间,互不相等,最多有10万个数据。
输出
升序输出排好序的数据,每行一个数字
样例输入
1
10
10
9
8
7
6
5
4
3
2
1
样例输出
1
2
3
4
5
6
7
8
9
10

#include "stdafx.h"
#include <cstdio>

const int maxn = 100010;

void merge(int num[], int l1, int r1, int l2, int r2)
{
	int i = l1, j = l2;
	int index = 0;
	int temp[maxn];
	while (i <= r1 && j <= r2)
	{
		if (num[i] < num[j])
		{
			temp[index++] = num[i++];
		}
		else
		{
			temp[index++] = num[j++];
		}
	}
	while (i <= r1)	temp[index++] = num[i++];
	while (j <= r2) temp[index++] = num[j++];
	for (int p = 0; p < index; p++)
	{
		num[l1 + p] = temp[p];
	}
}

void mergeSort(int num[], int left, int right)
{
	if (left < right)//这句别忘了!
	{
		int mid = (left + right) / 2;
		mergeSort(num, left, mid);
		mergeSort(num, mid + 1, right);
		merge(num, left, mid, mid + 1, right);
	}
}

int main()
{
	int n, m;
	int num[maxn];
	while (scanf("%d", &n) != EOF)
	{
		scanf("%d", &m);
		for (int i = 0; i < m; i++)
		{
			scanf("%d", &num[i]);
		}
		mergeSort(num, 0, m);
		for (int i = 1; i <= m; i++)//注意这个循环初始值和边界条件!
		{
			printf("%d\n", num[i]);
		}
	}
	return 0;
}

快速排序 qsort

题目描述
输入n个整数,用快速排序的方法进行排序
Input
第一行数字n 代表接下来有n个整数
接下来n行,每行一个整数
Output
Output
升序输出排序结果
每行一个数据
Sample Input
5
12
18
14
13
16
Sample Output
12
13
14
16
18
Hint
n<=5000
每个数据<=5000

#include <cstdio>
#include <time.h>
#include<stdlib.h>
#include<math.h>

const int maxn = 5010;

void swap(int num[], int a, int b)
{
	int temp = num[a];
	num[a] = num[b];
	num[b] = temp;
	//return;
}

int partition(int num[], int left, int right)
{
	int p = (round(1.0*rand()/ RAND_MAX * (right - left) + left));
	swap(num, left, p);
	int temp = num[left];
	while (left < right)
	{
		while (left<right&&num[right]>temp) right--;
		num[left] = num[right];
		while (left < right&&num[left] <= temp)	left++;
		num[right] = num[left];
	//两个while的顺序不能颠倒!!!!!
	}
	num[left] = temp;
	return left;
}

void quickSort(int num[], int left, int right)
{
	if (left < right)	//不是while!!!
	{
		int pos = partition(num, left, right);
		quickSort(num, left, pos - 1);
		quickSort(num, pos + 1, right);
	}
}

int main()
{
	srand((unsigned)time(NULL));
	int n;
	int num[maxn];
	while (scanf("%d", &n) != EOF)
	{
		for (int i = 0; i < n; i++)
		{
			scanf("%d", &num[i]);
		}
		quickSort(num, 0, n);
		for (int i = 1; i <= n; i++)//从1开始到n结束
		{
			printf("%d\n", num[i]);
		}
	}
	return 0;
}

注意:partition中两个while的顺序不能颠倒!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值