三、查找与排序(下)(1)

分治法

  • 将原问题划分成若干个规模较小而结构与原问题一致的子问题;递归地解决这些子问题,然后再合并其结果,就得到原问题的解。
  • 容易确定运行时间,是分治算法的优点之一。
  • 分治模式在每一层递归上都有三个步骤:
    • 分解(Divide):将原问题分解成一系列子问题;
    • 解决(Conquer):递归地解各子问题。若子问题足够小,则直接有解;
    • 合并(Combine):将子问题的结果合并成原问题的解。

分治的关键点

  • 原问题可以一直分解为形式相同的子问题,当子问题规模较小时,可自然求解,如一个元素本身有序
  • 子问题的解通过合并可以得到原问题的解
  • 子问题的分解以及解的合并一定是比较简单的,否则分解和合并所花的时间可能超过暴力解法,得不偿失

快速排序算法

  • 分解:数组A[p…r]被划分为两个子数组A[p…q-1]和A[q+1,r],使得A[q]为大小居中的数,左侧A[p…q-1]中的每个元素都小于等于它,而右侧A[q+1,r]中的每个元素都大于等于它。其中计算下标q也是划分过程的一部分。
  • 解决:通过递归调用快速排序,对子数组A[p…q-1]、A[q+1…r]进行排序
  • 合并:因为子数组都是原址排序的,所以 不需要合并,A[q…r]已经有序

思路:

QuickSort(A,p,r){
	if(p<r){
		q=Partition(A,p,r); //此步为关键
		QuickSort(A,p,q-1);
		QuickSort(A,q+1,r);
	}
}

划分算法

  • 一遍单向扫描法:
    • 一遍扫描法的思路是,用两个指针将数组划分为三个区间
    • 扫描指针(scan_pos)左边是确认小于等于主元的
    • 扫描指针到某个指针(nex_bigger_pos)中间为未知的,因此我们将第二个指针(next_bigger_pos)称为未知区间末指针,末指针的右边区间为确认大于主元的元素

伪代码:

partition(A,p,r):
	pivot = A[p]
	sp = p+1 // 扫描指针
	bigger = r //右侧指针
	while(sp<=bigger):
		if(A[sp]<=pivot) //扫描的元素小于主元,左指针向右移
			sp++
		else
			swap(sp,bigger)//扫描元素大于主元,二指针的元素交换,右指针左移
			bigger--
	swap(A[0],A[bigger])
	return bigger

完整代码:

#include<bits/stdc++.h>
using namespace std;
int Partition(vector<int> &A,int p,int r){
	int pivot = A[p];
	int sp = p+1; // 扫描指针
	int bigger = r; //右侧指针
	while(sp<=bigger){ 
		if(A[sp]<=pivot) //扫描的元素小于主元,左指针向右移
		{
			sp++;
		}else{
			swap(A[sp],A[bigger]);//扫描元素大于主元,二指针的元素交换,右指针左移
			bigger--;		
		}
	} 
	swap(A[p],A[bigger]);
	return bigger;	
}
void QuickSort(vector<int> &A,int p,int r){
	if(p<r){
		int q=Partition(A,p,r); //此步为关键
		QuickSort(A,p,q-1);
		QuickSort(A,q+1,r);
	}
}

int main()
{
	int arr[]={6,1,2,7,9,10,8,4,3,5};
	vector<int> vec(arr,arr+10); 
	QuickSort(vec,0,9);
	for(int i=0;i<10;i++){
		cout<<vec[i]<<endl;
	}
	return 0;
}
  • 双向扫描法:

头尾指针往中间扫描,从左找到大于主元的元素,从右找到小于等于主元的元素二者交换,继续扫描,直到左侧无大元素,右侧无小元素。

伪代码:

Partition2(A,p,r){
	pivot=A[p];
	left=p+1;
	right=r;
	//left不停往右走,直到遇到大于主元的元素
	while(left<=right){
		while(A[left]<=pivot&&left<=right) left++; //循环退出时,left一定是指向第一个大于主元的位置
		while(A[right]>pivot&&left<=right) right--;//循环退出时,right一定是指向最后一个小于等于主元的位置
		if(left<right)
			swap(A[left],A[right]);
	}
	//while退出时,两者交错,且right指向的是最后一个小于等于主元的位置,也就是主元应该呆的位置
	swap(A[p],A[right]);
	return right;
}

完整代码:

#include<bits/stdc++.h>
using namespace std;
int Partition2(vector<int> &A,int p,int r){//双向扫描法 
	int pivot = A[p];
	int left= p+1;
	int right= r;
	while(left<=right){
		while(A[left]<=pivot&&left<=right){
			left++;
		} 
		while(A[right]>pivot&&left<=right){
			right--;
		}
		if(left<right){
			swap(A[left],A[right]);
		}
	} 
	swap(A[p],A[right]);
	return right;
} 
void QuickSort(vector<int> &A,int p,int r){
	if(p<r){
		int q=Partition2(A,p,r); //此步为关键
		QuickSort(A,p,q-1);
		QuickSort(A,q+1,r);
	}
}

int main()
{
	int arr[]={6,1,2,7,9,10,8,4,3,5};
	vector<int> vec(arr,arr+10); 
	QuickSort(vec,0,9);
	for(int i=0;i<10;i++){
		cout<<vec[i]<<endl;
	}
	return 0;
}

快排在工程实践中的优化

  • 三点中值法

对pivot取值的改进

	//优化,在p,r,mid之间,选一个中间值作为主元
	int midIndex=(p+r)>>1; //中间下标
	int midValueIndex = -1;//中值下标 
	if(A[p]<=A[midIndex]&&A[p]>=A[r]){
		midValueIndex=p; 	
	}else if(A[r]<=A[midIndex]&&A[r]>=A[p]){
		midValueIndex=r; 
	}else{
		midValueIndex=midIndex;
	}
	swap(A[p],A[midValueIndex]);
	int pivot = A[p];
  • 绝对中值法

找出数组中的绝对中值

void insertSort(vector<int> &vec,int p,int r){
	for(int i=p;i<=r;i++){
		int temp=vec[i];
		int j=i-1;
		while(j>p-1&&temp<vec[j]){
			vec[j+1]=vec[j];
			j--;
		}
		vec[j+1]=temp;
	}
} 
//获取绝对的中值数,O(N)的样子
int getMedian(vector<int> A,int p,int r){
	int size=r-p+1; //数组长度
	//每五个元素一组
	int groupSize=(size%5==0)? (size/5):(size/5+1);
	//存储各小组的中值
	vector<int> medians(groupSize,0);
	int indexOfMedians = 0;
	//对 每一组进行插入排序
	for(int j=0;j<groupSize;j++){
		//单独处理最后一组,因为最后一组可能不满5个元素
		if(j==groupSize-1){
			insertSort(A,p+j*5,r);//排序最后一组
			medians[indexOfMedians] = A[(p+j*5+r)>>1];//最后一组的中间那个 
			indexOfMedians++;
		}else{
			insertSort(A,p+j*5,p+j*5+4);//排序非最后一组的某个组
			medians[indexOfMedians] = A[p+j*5+2]; //当前组(排序后)的中间那个 
			indexOfMedians++;
		}
	} 
	insertSort(medians,0,medians.size()-1);
	return medians[(medians.size())>>1];
} 

完整例子:

#include<bits/stdc++.h>
using namespace std;
void insertSort(vector<int> &vec,int p,int r){
	for(int i=p;i<=r;i++){
		int temp=vec[i];
		int j=i-1;
		while(j>p-1&&temp<vec[j]){
			vec[j+1]=vec[j];
			j--;
		}
		vec[j+1]=temp;
	}
} 
//获取绝对的中值数,O(N)的样子
int getMedian(vector<int> A,int p,int r){
	int size=r-p+1; //数组长度
	//每五个元素一组
	int groupSize=(size%5==0)? (size/5):(size/5+1);
	//存储各小组的中值
	vector<int> medians(groupSize,0);
	int indexOfMedians = 0;
	//对 每一组进行插入排序
	for(int j=0;j<groupSize;j++){
		//单独处理最后一组,因为最后一组可能不满5个元素
		if(j==groupSize-1){
			insertSort(A,p+j*5,r);//排序最后一组
			medians[indexOfMedians] = A[(p+j*5+r)>>1];//最后一组的中间那个 
			indexOfMedians++;
		}else{
			insertSort(A,p+j*5,p+j*5+4);//排序非最后一组的某个组
			medians[indexOfMedians] = A[p+j*5+2]; //当前组(排序后)的中间那个 
			indexOfMedians++;
		}
	} 
	insertSort(medians,0,medians.size()-1);
	return medians[(medians.size())>>1];
} 
int Partition2(vector<int> &A,int p,int r){//双向扫描法 
	//绝对中值法
	int midValue = getMedian(A,p,r);
	if(A[p]!=midValue){
		int midIndex=find(A.begin(),A.end(),midValue)-A.begin(); 
		swap(A[midIndex],A[p]); 	
	}
	int pivot = A[p];
	int left= p+1;
	int right= r;
	while(left<=right){
		while(A[left]<=pivot&&left<=right){
			left++;
		} 
		while(A[right]>pivot&&left<=right){
			right--;
		}
		if(left<right){
			swap(A[left],A[right]);
		}
	} 
	swap(A[p],A[right]);
	return right;
} 
void QuickSort(vector<int> &A,int p,int r){
	if(p<r){
		int q=Partition2(A,p,r); //此步为关键
		QuickSort(A,p,q-1);
		QuickSort(A,q+1,r);
	}
}

int main()
{
	int arr[]={6,1,2,7,9,15,8,4,3,5,11,14,12};
	vector<int> vec(arr,arr+13); 
	QuickSort(vec,0,12);
	for(int i=0;i<13;i++){
		cout<<vec[i]<<endl;
	}
	return 0;
}
  • 待排序列表较短时,用插入排序
void QuickSort(vector<int> &A,int p,int r){
	if(p<r){
		//带排序个数小于等于8的时候,插入排序
		if(p-r+1<=8){
			inserSort(A,p,r);
		}
		int q=Partition2(A,p,r); //此步为关键
		QuickSort(A,p,q-1);
		QuickSort(A,q+1,r);
	}
}

PS:一般推荐使用第一种和第三种。

归并排序

  • 归并排序(Merge Sort)算法完全依照了分治模式
    • 分解:将n个元素分成各含n/2个元素的子序列;
    • 解决:对两个子序列递归地排序;
    • 合并:合并两个已排序的子序列以得到排序结果
  • 和快排不同的是
    • 归并的分解较为随意
    • 重点是合并

伪代码:

扫描二维码关注公众号,回复: 9464028 查看本文章
MergeSort
	mergeSort(A,p,r){
		if(p<r){
			mid = (p+r)>>1;
			mergeSort(A,p,mid);
			mergeSort(A,mid+1,r);
			merge(A,p,mid,r);
		}
	}
	//创建辅助空间
	vector<int> helper(A.length);
	merge(A,p,mid,r){
		//先吧A中的数据拷贝到helper中
		helper.assign(A.begin(), A.end());
		left = p//左侧队伍的头部指针,指向待比较的元素
		right = mid+1//右侧队伍的头部指针,指向待比较的元素
		current = p // 原数组的指针,指向待填入数组的位置
		while(left<=mid&&right<=r){
			if(helper[left]<=helper[right]){
				A[current] = helper[left];
				current++;
				left++;
			}else{
				A[current] = helper[right];
				current++;
				right++;
			}
		}
		while(left<=mid){
			A[current]=helper[left];
			left++;
			current++;
		}
}	

完整打码:

#include<bits/stdc++.h>
using namespace std;
vector<int> helper; //定义全局变量helper向量
//定义模板函数求数组的长度
template <class T>
int getArrayLen(T& array)
{
	return (sizeof(array) / sizeof(array[0]));
}
void merge(vector<int> &A,int low,int middle,int high){
	//先把arr中的数据拷贝到helper中
	helper.assign(A.begin(),A.end());
	int left=low; //左侧队伍的头部指针,指向待比较的元素
	int right=middle+1;//右侧队伍的头部指针,指向待比较的元素
	int current=low;// 原数组的指针,指向待填入数组的位置
	while(left<=middle&&right<=high){ //当左右两边有一边未扫描完则循环
		if(helper[left]<=helper[right]){ //如果左边的元素小于右边则放入原数组
			A[current]=helper[left];
			current++;
			left++;
		}else{ //反之,右边的元素放入原数组
			A[current]=helper[right];
			current++;
			right++;
		}
	} 
	//如果左边都扫描完,右边没扫描完,因为辅助数组是拷贝的,右边元素即在原数组所以保持不动即可
	while(left<=middle){ //当右边都扫描完,左边还有剩余则把左边都放入原数组的剩下部分
		A[current]=helper[left];
		current++;
		left++;
	}
}
void mergeSort(vector<int> &A,int low,int high){
	if(low<high){
		int middle = (low+high)>>1; //求出中间
		mergeSort(A,low,middle); //对左边排序
		mergeSort(A,middle+1,high); //对右边排序
		merge(A,low,middle,high);	 //合并
	} 
}
int main()
{
	int arr[]={7,2,1,19,26,8,14,5,10,16};
	vector<int> A(arr,arr+getArrayLen(arr));
	mergeSort(A,0,getArrayLen(arr)-1);
	for(int i=0;i<A.size();i++){
		cout<<A[i]<<endl;
	}
	return 0;
}

算法案例

补充:算法题的五种解法

  • 举例法

先列举一些具体的例子,看看能否发现其中的一般规则。

  • 模式匹配法

将现有问题与相似问题作类比,看看能否通过修改相关问题的解法来解决新问题。

  • 简化推广法

首先,修改某个约束条件,从而简化这个问题,接着处理这个问题的简化版本,最后一旦找到解决简化版问题的算法,我们就可以基于这个问题进行推广,并试着调整简化版问题的解决方案,让它适用于这个问题的复杂版本。

  • 简单构造法

先从最基本的情况(比如n=1)来解决问题,一般只需几下正确的结果。得到n=1的结果后,接着设法解决n=2的情况。接下来,有了n=1和n=2的结果,就可以试着解决n=3…n=n的情况了

  • 数据结构头脑风暴法

我们可以快速过一遍数据结构的列表,然后逐一尝试各种数据结构。

题1:调整数组顺序使奇数位于偶数前面

输入一个整数数组,调整数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。要求时间复杂度为O(n)。

思路:
法一:归并的思想,开辟一块辅助空间,扫描数组,遇到奇数放在左边,遇到偶数放在最右边,同时指针各++,–,要耗费空间复杂度
法二:快排的思想,两个指针各从左、从右扫描,如果左边找到偶数,右边找到奇数则交换。

代码:

#include<bits/stdc++.h>
template <class T>
int getArrLen(T& array){
	return sizeof(array)/sizeof(array[0]);
}
using namespace std;
void OE_Sort(vector<int> &A){
	int left=0;
	int right=A.size()-1;
	while(left<=right){
		while(A[left]%2!=0&&left<=right){
			left++;
		}
		while(A[right]%2==0&&left<=right){
			right--; 
		} 
		if(left<=right){
			swap(A[left],A[right]);	
		}
	}
}
int main()
{
	int arr[]={7,1,6,3,8,2,4,9};
	vector<int> A(arr,arr+getArrLen(arr)); 
	OE_Sort(A);
	for(int i=0;i<A.size();i++){
		cout<<A[i]<<endl;
	}
	return 0;
}

题2:第k个元素

以尽量高的效率求出一个乱序数组中按数值顺序的第k个元素值

思路:
法1:先排序后找值,快排,O(NlogN)
法2:用快排分区的算法,找一个主元,左侧有序,右侧有序,若k正好等于该主元则返回,小于则在主元左侧继续找,大于则在主元右侧找。O(N)
代码:(法二)

#include<bits/stdc++.h>
#define random(n){rand()%n}
using namespace std;
template <class T>
int Len(T& arr){
	return sizeof(arr)/sizeof(arr[0]);
}
int partition(vector<int> &A,int p,int r){
	int mid = (p+r)>>1;
	int midIndex=-1;
	if(A[mid] >= A[p] && A[mid] <=A[r]){
		midIndex = mid;
	}else if(A[p] >= A[mid] && A[p] <=A[r]){
		midIndex = p;
	}else{
		midIndex = r;
	}
	swap(A[p],A[midIndex]);
	int pivot = A[p];
	int left = p+1;
	int right = r;
	while(left<=right){
		while(A[left]<=pivot&&left<=right){
			left++;
		}
		while(A[right]>=pivot&&left<=right){
			right--;
		}
		if(left<right){
			swap(A[left],A[right]);
		}
	}
	swap(A[p],A[right]);
	return right;
}
int selectK(vector<int> A,int p,int r,int k){
	int q = partition(A,p,r);//主元的下标 
	int qK = q-p+1;//主元是第几个元素
	if(qK==k){
		return A[q];
	}else if(qK>k){
		return selectK(A,p,q-1,k);
	}else{
		return selectK(A,q+1,r,k-qK);//原数组的第k个元素,在右侧中是第k-qK个元素 
	}
	 
}
int main()
{
	srand(int(time(0)));
	vector<int> A;
	for(int i=0;i<100000;i++){
		A.push_back(random(100000));
	}
	cout<<selectK(A,0,A.size()-1,100000);
	return 0;
}

题3:超过一半的数字

数组中有一个数字出现的次数超过了数组长度的一半,找出这个数字。

思路:
法1:排序之后,这个数因为超过数组长度的一半,所以必定在N/2处,因此排序后返回arr[N/2]即可O(NlogN)
法2:顺序统计,O(N) 具体参考题2(限制:需要改动原数组)
法3:不同的数,进行消除
代码:

#include<bits/stdc++.h>
using namespace std;
template <class T>
int len(T& arr)
{
	return sizeof(arr)/sizeof(arr[0]);
}
int f1(vector<int> &arr){
	sort(arr.begin(),arr.end());
	return arr[arr.size()/2];
}
int partition(vector<int> &A,int p,int r){
	int mid = (p+r)>>1;
	int midIndex=-1;
	if(A[mid] >= A[p] && A[mid] <=A[r]){
		midIndex = mid;
	}else if(A[p] >= A[mid] && A[p] <=A[r]){
		midIndex = p;
	}else{
		midIndex = r;
	}
	swap(A[p],A[midIndex]);
	int pivot = A[p];
	int left = p+1;
	int right = r;
	while(left<=right){
		while(A[left]<=pivot&&left<=right){
			left++;
		}
		while(A[right]>=pivot&&left<=right){
			right--;
		}
		if(left<right){
			swap(A[left],A[right]);
		}
	}
	swap(A[p],A[right]);
	return right;
}
int selectK(vector<int> A,int p,int r,int k){
	int q = partition(A,p,r);//主元的下标 
	int qK = q-p+1;//主元是第几个元素
	if(qK==k){
		return A[q];
	}else if(qK>k){
		return selectK(A,p,q-1,k);
	}else{
		return selectK(A,q+1,r,k-qK);//原数组的第k个元素,在右侧中是第k-qK个元素 
	}
	 
}
int f2(vector<int> arr){
	return selectK(arr,0,arr.size()-1,arr.size()/2);
}
int f3(vector<int> arr){
	//候选数
	int candidate=arr[0]; 
	//出现的次数 
	int nTimes=1;
	for(int i=1;i<arr.size();i++){
		//两两消减为0,应该把现在的元素作为候选值 
		if(nTimes==0){
			candidate=arr[i];
			nTimes=1;
			continue;
		}
		//遇到和候选值相同的,次数+1
		if(arr[i]==candidate){
			nTimes++; 
		} 
		//不同的数,进行消减
		else{
			nTimes--;
		} 
	}
	return candidate;
}
int main()
{
	int arr[]={2,1,1,3,0,1,1,1};
	vector<int> vec(arr,arr+len(arr));
	cout<<"法1:"<<f1(vec)<<endl;
	cout<<"法2:"<<f2(vec)<<endl; 
	cout<<"法3:"<<f3(vec)<<endl; 
	return 0;
}

题3拓展:寻找发帖“水王”

有一个列表包含了所有帖子的作者ID,“水王”的ID也在其中,恰好为总数的一半,你能快速找出他吗

思路:
消除法改进
水王占总数的一半,说明总数必为偶数
不失一般性,假设隔一个数就是水王的id,两两不同最后一定为消减为0
水王可能是最后一个元素,每次扫描的时候,多一个动作,和最后一个元素作比较,单独计数,计数恰好等于一半、
如果不是,计数不足一半,那么去掉最后一个元素,水王就是留下的那个candidate
代码:

#include<bits/stdc++.h>
using namespace std;
template <class T>
int len(T& arr){
	return sizeof(arr)/sizeof(arr[0]);
}
int fun(vector<int> &arr){
	int candidate = arr[0];
	int nTimes=1;
	int countOfLast=1;//统计最后这个元素出现的次数
	int N=arr.size(); 
	for(int i=1;i<N;i++){
		//增加和最后一个元素比较的步骤
		if(arr[i]==arr[N-1]){
			countOfLast++;
		} 
		if(nTimes==0){
			candidate = arr[i];
			nTimes=1;
			continue;
		}
		if(arr[i]==candidate){
			nTimes++;
		}else{
			nTimes--;
		}
	}
	if(countOfLast==N/2){
		return arr[N-1];		
	}
	else{
		return candidate;
	}
}
int main()
{
	int arr[]={2,5,5,5,1,5,1,3};
	vector<int> vec(arr,arr+len(arr));
	cout<<fun(vec);
	return 0;
}

题4:最小可用ID

在非负数组(乱序)中找到最小的可分配的id(从1开始编号),数据量1000000
例如:
数组{3,2,1,4,5,8,9,6,7,11…}之后都没有再出现10则10为最小可分配ID
数组{1,2,3,4,5…N} 则最小可分配ID为N+1

思路:
法1:暴力法,从i=1开始看数组中是否存在i,不存在则返回i,否则i++直到找到为止。(不可取)

int find1(vector<int> arr){
	int i=1;
	while(1){
		if(!find(arr.begin(),arr.end(),i)){
			return i;
		}
		i++;
	}
}

法2:先排序后找 O(NlogN)

int find2(vector<int> arr){
	sort(arr.begin(),arr.end());
	int i=0;
	while(i<arr.size()){
		if(i+1!=arr[i]){
			return i+1;
		} 
		i++;
	}
	return i+1;
}

法3:借助辅助空间
新建长为n+1的数组F,初始值全为false,扫描原数组中的元素,小于n则将F[A[i]]记录为true
最后再扫描F,返回第一个为false的元素的下标 注:有点类似于计数排序O(n)但是浪费空间

int find3(vector<int> arr){
	int n = arr.size();
	vector<int> helper(n+1,0);//初始化vector,第一个参数为长度,第二个为初始值 
	for(int i=0;i<n;i++){
		if(arr[i]<=n){
			helper[arr[i]]=1;			
		}
	}
	for(int i=1;i<=n;i++){
		if(helper[i]==0){
			return i;
		}
	}
	return n+1;
}

法4:如果不允许使用辅助空间,用partition
例如:数组一共有100个长度,假设是有序的,如果中间的索引50的值为50,则说明左侧是没有空隙的,继续在右侧找,否则在左侧找。
代码:

int partition(vector<int> &A,int p,int r){
	int mid = (p+r)>>1;
	int midIndex=-1;
	if(A[mid] >= A[p] && A[mid] <=A[r]){
		midIndex = mid;
	}else if(A[p] >= A[mid] && A[p] <=A[r]){
		midIndex = p;
	}else{
		midIndex = r;
	}
	swap(A[p],A[midIndex]);
	int pivot = A[p];
	int left = p+1;
	int right = r;
	while(left<=right){
		while(A[left]<=pivot&&left<=right){
			left++;
		}
		while(A[right]>=pivot&&left<=right){
			right--;
		}
		if(left<right){
			swap(A[left],A[right]);
		}
	}
	swap(A[p],A[right]);
	return right;
}
int selectK(vector<int> A,int p,int r,int k){
	int q = partition(A,p,r);//主元的下标 
	int qK = q-p+1;//主元是第几个元素
	if(qK==k){
		return A[q];
	}else if(qK>k){
		return selectK(A,p,q-1,k);
	}else{
		return selectK(A,q+1,r,k-qK);//原数组的第k个元素,在右侧中是第k-qK个元素 
	}
	 
}
int find4(vector<int> &arr,int l,int r){
	if(l>r)
		return l+1;
	int midIndex = (l+r)>>1;
	int q= selectK(arr,l,r,midIndex-l+1);//实际在中间位置的值
	int t=midIndex+1;
	if(q==t){//左侧紧密
		return find4(arr,midIndex+1,r); 
	}else{
		return find4(arr,l,midIndex-1);
	}
	
} 

题5:合并有序数组

给定两个排序后的数组A和B,其中A的末端有足够的缓冲空间容纳B。编写一个方法,将B合并入A并排序

思路:
指定一个current指针指向A中A+B-1的位置,同时有两个指针A、B分别指向A、B的末端,扫描A、B,将大的放入A[current]
同时current向左移、大的指针向右移,直到current指针等于0。

代码:

#include<bits/stdc++.h>
using namespace std;
template <class T>
int len(T& arr){
	return sizeof(arr)/sizeof(arr[0]);
}

int main()
{
	int arr1[]={1,4,6,8,10,13,14};
	int arr2[]={2,4,5,7,11,12,13};
	int l1=len(arr1);
	int l2=len(arr2);
	vector<int> vec(arr1,arr1+l1+l2);
	int current =l1+l2-1;
	int a=l1-1;
	int b=l2-1;
	while(current!=0){
		if(arr1[a]>=arr2[b]){
			vec[current]=arr1[a];
			current--;
			a--;
		}else{
			vec[current]=arr2[b];
			current--;
			b--;
		}
	}
	for(int i=0;i<vec.size();i++){
		cout<<vec[i]<<endl;
	}
	return 0;
} 

题6:逆序对个数

一个数列,如果左边的数大,右边的数小,则称这两个数为一个逆序对。求出一个数列中有多少个逆序对。

思路:
使用归并排序,在merge的过程中,只要提取到右侧的数,那么就有逆序对,数量是左侧队伍的元素的个数
代码:

#include<bits/stdc++.h>
using namespace std;
vector<int> helper;
template <class T>
int getArrayLen(T& array)
{
	return (sizeof(array) / sizeof(array[0]));
}
int nCount=0; 
void merge(vector<int> &A,int low,int middle,int high){
	//先把arr中的数据拷贝到helper中
	helper.assign(A.begin(),A.end());
	int left=low;
	int right=middle+1;
	int current=low;
	while(left<=middle&&right<=high){
		if(helper[left]<=helper[right]){
			A[current]=helper[left];
			current++;
			left++;
		}else{//右边小 
			A[current]=helper[right];
			current++;
			right++;
			nCount+=middle-left+1;
		}
	} 
	while(left<=middle){
		A[current]=helper[left];
		current++;
		left++;
	}
}
void mergeSort(vector<int> &A,int low,int high){
	if(low<high){
		int middle = (low+high)>>1;
		mergeSort(A,low,middle);
		mergeSort(A,middle+1,high);
		merge(A,low,middle,high);	
	} 
}
int main()
{
	int arr[]={7,2,1,19,26,8,14,5,10,16};
	vector<int> A(arr,arr+getArrayLen(arr));
	mergeSort(A,0,getArrayLen(arr)-1);
	cout<<"逆序对"<<nCount<<endl; 
	return 0;
}
发布了25 篇原创文章 · 获赞 0 · 访问量 1501

猜你喜欢

转载自blog.csdn.net/u014681799/article/details/86771445
今日推荐