Part 2.2 排序算法

题单

P1177 【模板】快速排序

思路

我们要做到的其实就是对在不借助sort函数的情况下对a[1]……a[n]进行从小到大的排序,而快排的精髓就是针对a[l]到a[r]的元素先找一个基准元素(这个任意,但我用的是a[(l + r) / 2]),然后使得在经过操作后使得在基准元素左边的元素全部小于基准元素,在基准元素右边的元素全部大于基准元素,从而使得基准元素“归位”,当所有元素“归位”时,排序便完成了。

代码

void qsort (int l, int r) 
{
	int mid = a[(l + r) / 2]; // 找基准元素
	int i = l, j = r; //i站在最左边向右边开始搜索,j相反
	while (i <= j) //当i与j“碰头”(i == j)时说明整个数列已被遍历
	{
		while (a[i] < mid) i++;//找到第一个在左边大与基准元素的元素
		while (a[j] > mid) j--;//找到第一个在右边小于基准元素的元素
		if (i <= j) 
		{
			swap (a[i], a[j]); //把大的元素换到右边,小的元素换到左边,以逐渐实现归位
			i ++, j --;//继续遍历看看还有我们想要的嘛
		}
	}
	if (j > l) qsort (l, j); //j > l说明a[1]到a[j]还需要再排序
	if (i < r) qsort (i, r); //同理
}

注:基准元素的位置并非不变的,而是在变的;快排的最坏复杂度可以达到O(n^2)

P1059 [NOIP2006 普及组] 明明的随机数

思路

似乎只是个简单的桶排序?(好像用set也可以实现)

代码

    scanf ("%d", &n);
	int x;
	for (int i = 1; i <= n; i++) 
	{
		scanf ("%d", &x);
		book[x] ++;
	}
	int ans = 0;
	for (int i = 1; i <= 1000; i++) 
	{
		if (book[i]) 
		{
			ans ++;
		}
	}
	printf ("%d\n", ans);
	for (int i = 1; i <= 1000; i++) 
	{
		if (book[i]) 
		{
			printf ("%d ", i);
		}
	}

P1068 [NOIP2009 普及组] 分数线划定

思路

觉得考察的就是对结构体数组如何用sort函数进行排序,即添加cmp函数;至于分数相同时小号码优先,只需改一下cmp函数即可

代码

struct player
{
	int s, name;
} a[maxn]; 
bool cmp (player a, player b) 
{
	if (a.s == b.s) return a.name < b.name;
	return a.s > b.s;
}
int n, m, ans;
int main ()
{
	scanf ("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) 
	{
		scanf ("%d %d", &a[i].name, &a[i].s);
	}
	sort (a + 1, a + n + 1, cmp);
	int grade = a[m * 3 / 2].s;
	for (int i = 1; i <= n; i++) 
	{
		if (a[i].s >= grade) 
		{
			ans ++;
		}
		else 
		{
			break;
		}
	}
	printf ("%d %d\n", grade, ans);
	for (int i = 1; i <= n; i++) 
	{
		if (a[i].s >= grade) 
		{
			printf ("%d %d\n", a[i].name, a[i].s);
		}
		else 
		{
			break;
		}
	}
	return 0;
}

P1051 [NOIP2005 提高组] 谁拿了最多奖学金

思路

简单地把每个奖项地获取条件写出来即可,似乎有点像模拟?

const int maxn = 105;
struct students 
{
	string name;
    int money, turn; 
} a[maxn]; 
bool cmp (students a, students b) 
{
	if (a.money == b.money) return a.turn < b.turn;
	else return a.money > b.money;
}
int n, g, c, d;
char west, chair;
int main ()
{
	scanf ("%d", &n);
	for (int i = 1; i <= n; i++) 
	{
		a[i].turn = i;
		cin >> a[i].name;
		scanf ("%d %d %c %c %d", &g, &c, &chair, &west, &d);
		if (g > 80 && d >= 1) 
		{
			a[i].money += 8000;
		}
		if (g > 85 && c > 80) 
		{
			a[i].money += 4000;
		}
		if (g > 90) 
		{
			a[i].money += 2000;
		}
		if (g > 85 && west == 'Y') 
		{
			a[i].money += 1000;
		}
		if (c > 80 && chair == 'Y') 
		{
			a[i].money += 850;
		}
	}
	sort (a + 1, a + n + 1, cmp);
	int tot = 0;
	for (int i = 1; i <= n; i++)
	{
		tot += a[i].money;
	}
	cout << a[1].name;
	printf ("\n%d\n%d", a[1].money, tot);
	return 0;
}

P1309 [NOIP2011 普及组] 瑞士轮

错误思路一

最简(暴)单(力)、好想的方法,就是每次用sort()函数进行排序,但是sort()函数的最优时间复杂度是O(nlogn),最差为O(n^2),这就导致题目的时间复杂度达到O(rnlogn)到O(rn*n),所以肯定会TLE,即使用O2也不管用。

错误思路二

采用归并排序的方法,达到稳定的O(rnlogn),但还是会TLE,只不过比错误思路一快一些,并在c++20和O2的助力下可以卡过去。

正确思路

通过归并排序的核心思想,我们可以先对储存着选手信息的结构体数组进行排序,这样这个数组就是有序的了,那么在接下来的每一轮比赛中,按照第一名到第2n名的顺序,把胜者分到一个组(即数组),把败者分到一个组,然后再用归并排序的方法把2个数组归并再一起,这样一轮比赛后的排序就完成了。时间复杂度就变成了O(rn)。
Q:为什么这么分胜者组与败者组是对的?
A:因为胜者组每一个人都是加了一分,败者组分数都不变,而比赛的顺序是由第一名到第2n名的,故胜者组和败者组中都保持着排名由高到低的顺序,满足归并排序的条件,故可以。

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100050;
struct player 
{
	int s, turn, w;
} a[maxn * 2], win[maxn * 2], l[maxn * 2];
bool cmp (player a, player b) 
{
	if (a.s == b.s) return a.turn < b.turn;
	return a.s > b.s;
}
int n, r, q, pw, pl;
void merge () 
{
	int i = 1, j = 1, p = 1;
	while (i <= n && j <= n) 
	{
		if (cmp (win[i], l[j])) 
		{
			a[p] = win[i];
			i ++, p ++;
		}
		else 
		{
			a[p] = l[j];
			j ++, p ++;
		}
	}
	while (i <= n) 
	{
		a[p] = win[i];
		p ++, i ++;
	}
	while (j <= n) 
	{
		a[p] = l[j];
		p ++, j ++;
	}
}
int main ()
{
	scanf ("%d %d %d", &n, &r, &q);
	for (int i = 1; i <= 2 * n; i++) 
	{
		a[i].turn = i;
		scanf ("%d", &a[i].s);
	}
	for (int i = 1; i <= 2 * n; i++) 
	{
		scanf ("%d", &a[i].w);
	}
	sort (a + 1, a + 2 * n + 1, cmp); 
	for (int j = 1; j <= r; j++) 
	{
	    pl = 1, pw = 1;
		for (int i = 1; i <= 2 * n; i += 2) 
		{
			if (a[i].w > a[i + 1].w) 
			{
				a[i].s ++;
				win[pw] = a[i];
				l[pl] = a[i + 1];
				pw ++, pl ++;
			}
			else 
			{
				a[i + 1].s ++;
				win[pw] = a[i + 1];
				l[pl] = a[i];
				pw ++, pl ++;
			}
		}
		merge ();
	}
	printf ("%d", a[q].turn);
	return 0;
}

收获

学习了归并排序
②懂得了要熟练运用算法中的思想

P1908 逆序对

思路一(归并排序做法)

我们现在有下标为[l, mid](记为区间A)和[mid + 1, r](记为区间B)的两个区间的元素,且A、B两个区间的元素均为由小到大排列。尽管元素下标可能并非题目给的初始下标,但是根据归并排序逐层向下划分区间的方法,我们可得A中元素初始下标一定小于B中元素初始下标。下面我们先让下标j不动,去移动下标i,如果a[i]<=a[j],说明a[i]无法与a[j]、a[j + 1]……a[r]产生逆序对(那a[i]可以与j前面的元素产生逆序对怎么办?看下文即可),那么就换a[i + 1]这个比a[i]大的元素试试;如果a[i]>a[j],说明a[i]、a[i + 1]……a[mid]均可与a[j]产生逆序对,这时,我们直接记录下这(mid - i + 1)对逆序对,并时时a[j + 1]与a[i]是否还能产生逆序对,这就是下一次while循环要办的事了。 那么是如何做到区间中元素都由小到大排列呢?归并排序正常进行就好了

代码一(归并排序做法)

#include <bits/stdc++.h>
using namespace std;
const int maxn = 500005;
int a[maxn], temp[maxn], n;
long long ans; 
void msort (int l, int r) 
{
	if (l == r) 
	{
		return;
	}
	int mid = (l + r) / 2;
	msort (l, mid);
	msort (mid + 1, r);
	int i = l, j = mid + 1, p = l;
	while (i <= mid && j <= r) 
	{
		if (a[i] <= a[j]) 
		{
			temp[p] = a[i];
			p ++, i ++;
		}
		else 
		{
			temp[p] = a[j];
			p ++, j ++;
			ans += (mid - i + 1);
		}
	}
	while (i <= mid) 
	{
		temp[p ++] = a[i ++];
	}
	while (j <= r) 
	{
		temp[p ++] = a[j ++];
	}
	for (int i = l; i <= r; i++) 
	{
		a[i] = temp[i];
	}
} 
int main ()
{
	scanf ("%d", &n);
	for (int i = 1; i <= n; i++) 
	{
		scanf ("%d", &a[i]);
	}
	msort (1, n);
	printf ("%lld", ans);
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值