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;
}