排序算法 & 分治法

文章介绍了几种常见的排序算法,包括选择排序、插入排序、归并排序和快速排序的实现细节以及它们的时间复杂度。其中,选择和插入排序在最坏情况下具有O(n^2)的时间复杂度,而归并排序保持稳定的O(nlogn)。快速排序在平均情况下也是O(nlogn),但最坏情况为O(n^2)。此外,文章还提到了最大子数组问题的分治法和动态规划解法,以及逆序对计数的归并排序应用。
摘要由CSDN通过智能技术生成

选择排序 O(n^2)

void Csort(int * data,int length)
{
	int index;//当前最小值的索引
	int item;//当前最小值
	for (int i = 0; i < length; i++)
	{
		item = data[i];
		index = i;
		for (int w = i + 1; w < length; w++)
		{
			if (item > data[w])//如果找到更小的值,更新最小值
			{
				item = data[w];
				index = w;
			}
		}
		data[index] = data[i];//将最小值放在相应位置
		data[i] = item;//
	}
}

插入排序  O(n^2)

template<typename T>
void insertion_sort(T* A, int len) {
    T key;
    int i, j;
    for ( i = 1; i < len; i++) {
        key = A[i];
        j = i - 1;
        while ((j >= 0) && (key < A[j])) {
            A[j + 1] = A[j];
            A[j] = key;
            j--;
        }
    }
}

归并排序 O(nlogn)

伪代码:

void merge_sort(int q[],int l,int r)
{
    if(l >= r)return ; //递归结束条件 
    int mid=l+r>>1; //折半递归 
    
    merge_sort(q,l,mid);  //左边递归 
    merge_sort(q,mid+1,r);  //右边递归 
  
    int k=0,i=l,j=mid+1;  //tmp是一个暂时存储元素的数组 
    while(i<=mid&&j<=r)
      if(q[i] <= q[j]) tmp[k++]=q[i++];
      else tmp[k++]=q[j++];
    
    while(i<=mid) //右半部分剩余没有合并的元素 
        tmp[k++] = q[i++];
    while(j<=r)   //左半部分剩余没有合并的元素 
        tmp[k++] = q[j++];

    for(i=l,j=0;i<=r;i++,j++)
        q[i] = tmp[j];  //将存在tmp数组的元素重新放回原数组中 
}

快速排序

 

void quick_sort(int* a,int l, int r)//a为待细分组,l和r分别为左右索引
{
    if (l >= r) return;

    int mid_num = a[l]; //获取中介元素
    int i = l, j = r;
    while (i < j)
    {
        while (a[j] >= mid_num && i < j) j--;  //从右向左判断,找到小于中介的值
        if (i < j) a[i] = a[j];
        while (a[i] <= mid_num && i < j) i++;//从左向右判断,找到大于中介的值
        if (i < j) a[j] = a[i];
    }
    a[i] = mid_num;      //循环结束,将中间值放入数组
    quick_sort(a, l, i - 1);
    quick_sort(a, i + 1, r);
}

算法最好最坏情况总结

算法最好情况最差情况
冒泡排序已排好,不交换每次比较都要交换
插入排序排序前已排好,比较次数n-1,元素移动次数为0每一次比较都要做数据移动
快速排序每次划分对一个元素定位后,左右两侧序列长度相同,则下一步就是对两个长度减半的子序列进行排序待排序元素已经按照其排序码从小到大排好序,其递归树成为单支树

计算时间复杂度 (递归树法、代入法、主定理法) 

 

有关排序算法的相关问题分析

次序选择问题

 

 

#include<iostream>
#include<time.h>
using namespace std;

// 随机选取主元并且把数组按照主元分解,并返回主元最后的位置(与快排完全一致)
int Randomized_Partition(int A[], int l, int r)
{
	int p = 0;                          // 交换媒介
	int P1 = l;
	int P2 = l;                         // 两个遍历下标 
	srand((time(NULL)));
	int s = rand() % (r - l) + 1 + l;   // 随机选取主元

	// 交换主元到末尾
	p = A[s];
	A[s] = A[r];
	A[r] = p;

	// 遍历分解原数组
	while (P2 < r)
	{
		// P2遍历到比主元小的,就把这个元素放到左边
		if (A[P2] <= A[r])
		{
  			p = A[P1];
			A[P1] = A[P2];
			A[P2++] = p;
			++P1;
		}
		// 如果没有,就直接把P2往下;
		else ++P2;
	}

	// 把主元放中间
	p = A[r];
	A[r] = A[P1];
	A[P1] = p;

	// 返回主元现在的位置
	return P1;
}

// 进行次序选择
int Randomized_Selection(int A[], int k,int l, int r)
{
	// 递归出口
	if (l >= r)
		return 0;

	// 分解原数组,并返回主元位置
	int p = Randomized_Partition(A, l, r);
	int x = 0;       // 保存结果

	// 判断是否找到的主元是我们要找的第k小的位置,注意这里的比较的表达式
	if (p-l+1 == k)
		return A[p];

	// 如果结果比k小,就找右边,注意这时候的k要减掉左边的元素
	if (p - l + 1 < k)
		x=Randomized_Selection(A, k - (p - l + 1), p +1, r);

	else
		x=Randomized_Selection(A, k, l, p-1);

	return x;

}

int main()
{
	int A[6] = { 3,2,5,6,0,4 };
	cout << "原数组为:";
	for (int i = 0; i < 6; ++i)
		cout << A[i] << " ";

	// 找第二小的元素
	int ans=Randomized_Selection(A,2, 0, 5);

	cout << "第二小的元素为:"<<ans<<endl;
	return 0;
}

最大子数组 O(nlogn)

s3的求法

 

最大子数组各种算法比较:

//分治法
#include <iostream>
#include <climits>
using namespace std;
 
int a[16] = {13,-3,-25,20,-3,-16,-23, 18,20,-7,12 ,-5,-22,15,-4,7};  
 
int findCrossbigSubarray(int low,int mid,int high)		//和一定要包含a[mid]和a[mid+1] 
{
	int sum = 0;
	int left_big = -INT_big;
	for(int i=mid; i>=low; --i)							//mid属于左数组
	{
		sum += a[i];
		if(sum > left_big) left_big = sum;
	}
 
	sum = 0;
	int right_big = -INT_big;
	for(int i=mid+1; i<=high; ++i)						//mid+1属于右数组 
	{
		sum += a[i];
		if(sum > right_big) right_big = sum;
	}
	
	return right_big + left_big;
}
 
int findbigSubarray(int low, int high)
{
	if(low == high) return a[low];
	int mid = (low + high) / 2;
 
	int left_big = findbigSubarray(low,mid);				//左数组的最大值
	int right_big = findbigSubarray(mid+1,high);			//右数组的最大值
	int cross_big = findCrossbigSubarray(low,mid,high);		//穿越数组的最大值
 
	int big = big(left_big,right_big);
	if(cross_big > big) big = cross_big;
 
	return big;												//返回三者的最大值
}
 
int main()
{
	cout<<findbigSubarray(0,15);
	return 0;
}
//动态规划算法
//动态转移方程:
//sum[i+1] = max(sum[i]+a[i+1], a[i+1])

#include <iostream>
using namespace std;
 
int a[16] = {13,-3,-25,20,-3,-16,-23, 18,20,-7,12 ,-5,-22,15,-4,7};
 
	//sum = {13,10,-15,20,17, 1, -22, 18,38,31,43 ,38, 16,31,27,34};
int sum[16] = {a[0]};
 
int main()
{
	for(int i=0; i<16; ++i)
		sum[i+1] = max(sum[i]+a[i+1], a[i+1]); 
 
	//取sum中的最大值
	int Max = sum[0];
	for(int i=1; i<16; ++i)
		if(sum[i] > Max)
			Max = sum[i];
 
	cout<<Max;
	return 0;
}

 逆序对计数问题

#include <bits/stdc++.h>
using namespace std;
int n, a[5000001], b[5000001];
long long ans;
inline void msort(int l, int r)//归并排序
{
    int mid = (l + r) / 2;//取中间 
    if(l == r)//若l == r了,就代表这个子序列就只剩1个元素了,需要返回 
    {
        return;
    }
    else
    {
        msort(l, mid);//分成l和中间一段,中间 + 1和r一段(二分) 
        msort(mid + 1, r);
    }
    int i = l;//i从l开始,到mid,因为现在排序的是l ~ r的区间且要二分合并 
    int j = mid + 1;//j从mid + 1开始,到r原因同上
    int t = l;//数组b的下标,数组b存的是l ~ r区间排完序的值 
    while(i <= mid && j <= r)//同上i,j的解释 
    {
        if(a[i] > a[j])//如果前面的元素比后面大(l ~ mid中的元素 > mid + 1 ~ r中的元素)(逆序对出现!!!) 
        { 
            ans += mid - i + 1;//由于l ~ mid和mid + 1 ~ r都是有序序列所以一旦l ~ mid中的元素 > mid + 1 ~ r中的元素而又因为第i个元素 < i + 1 ~ mid那么i + 1 ~ mid的元素都 > 第j个元素。所以+的元素个数就是i ~ mid的元素个数,及mid - i + 1(归并排序里没有这句话,求逆序对里有) 
            b[t++] = a[j];//第j个元素比i ~ mid的元素都小,那么第j个元素是目前最小的了,就放进b数组里 
            ++j;//下一个元素(mid + 1 ~ r的元素小,所以加第j个元素) 
        }
        else
        {
            b[t++] = a[i];//i小,存a[i] 
            ++i;//同理 
        }
    }
    while(i <= mid)//把剩的元素(因为较大所以在上面没选) 
    {
        b[t++] = a[i];//存进去 
        ++i; 
    }
    while(j <= r)//同理 
    {
        b[t++] = a[j];
        ++j;
    }
    for(register int i = l; i <= r; ++i)//把有序序列b赋值到a里 
    {
        a[i] = b[i];
    }
    return;
}
int main()
{
    scanf("%d", &n);
    for(register int i = 1; i <= n; ++i)
    {
        scanf("%d", &a[i]);
    }
    msort(1, n);//一开始序列是1 ~ n 
    printf("%lld", ans);
    return 0;
}

 分治法的其他应用

快速幂

double exp2(double a, int n)
{
    if(a==0) return 0;
    if(n<=0) return 1;
    else{
        int x = exp2(a,n/2);
        if(n%2==1) return a*x*x;
        else return x*x;
    }
}

 二分查找算法

int BiSearch(int a[],const int& x,int l,int r)
{
    if(r>=l){
        int m = (l+r)/2;
        if(x==a[m]) return m;
        else if(x<a[m]) return BiSearch(a,x,l,m-1);
        else return BiSearch(a,x,m+1,r);
    }
    else return -1;
}

棋盘覆盖问题

#include<iostream>
#define S 8
using namespace std;

int number = 1;//给每个L型骨牌一个编号
int board[S][S];//储存棋盘

//t1、t2分别是棋盘左上角的横坐标和纵坐标
//d1、d2分别是棋盘内特殊方块的横坐标和纵坐标
//size表示棋盘的大小

void chessboard(int t1 ,int t2,int d1, int d2, int size) {
    
	if (size == 1)return; //递归终止条件
	int s = size / 2;//分隔棋盘
	int n = number++;
	
    //左上角小棋盘:
	if ((d1 < t1 + s) && (d2 < t2 + s)) //判断棋盘内是否有特殊方块
    {
		chessboard(t1, t2, d1, d2, s);//如果有,就继续递归
	}
	else {

		board[t1 + s - 1][t2 + s - 1] = n;//在棋盘右下角放一个L型骨牌,并把它当做特殊方块
	
		chessboard(t1, t2, t1 + s - 1, t2 + s - 1, s);
	}
	//右上角小棋盘
	if ((d1 < t1 + s) && (d2 >= t2 + s)) 
    {
		
		chessboard(t1, t2 + s, d1, d2, s);
	}
	else {
		
		board[t1 + s - 1][t2 + s] = n;//在棋盘左下角放一个L型骨牌,并把它当做特殊方块
	
		chessboard(t1, t2 + s, t1 + s - 1, t2 + s, s);
	}
	//左下角小棋盘
	if (d1 >= t1 + s && d2 < t2 + s) {
		
		chessboard(t1 + s, t2, d1, d2, s);
	}
	else {
	
		board[t1 + s][t2 + s - 1] = n;//在棋盘右上角放一个L型骨牌,并把它当做特殊方块
		
		chessboard(t1 + s, t2, t1 + s, t2 + s - 1, s);
	}
	//右下角小棋盘
	if (d1 >= t1 + s && d2 >= t2 + s) {
		
		chessboard(t1 + s, t2 + s, d1, d2, s);
	}
	else {
		
		board[t1 + s][t2 + s] = n;//在棋盘左上角放一个L型骨牌,并把它当做特殊方块
		
		chessboard(t1 + s, t2 + s, t1 + s, t2 + s, s);
	}
}



//测试代码
int main()
{
	// 将棋盘内每个方块初始化为0
	for(int i = 0; i < S; i++) {
		for(int j = 0; j < S; j++) {
			board[i][j] = 0;
		}
	}

	
	chessboard(0,0,1,2,S);

	//输出测试结果
	for(int i=0;i<S;i++)
	{
		for (int j = 0; j < S; j++) {
			cout << board[i][j] << "\t";
		}
		cout << endl;
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值