引例
给定一个递增的正整数序列和一个正整数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-路归并排序)
用递归和非递归两种方法实现
- 递归
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);//将左子区间和右子区间合并
}
}
- 非递归
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的顺序不能颠倒!!!