快速排序、sort、qsort、拓展排序

目录

一,快速排序

1,原理

2,不稳定快速排序

3,稳定快速排序

4,弱序关系和严格弱序关系

(1)弱序关系(weak ordering)

(2)严格弱序关系(strict weak ordering)

(3)序关系最小集

(4)快速排序依赖的序关系

5,快速排序总结

二,C++ sort

1,sort使用

2,sort源码

3,sort原理

4,时间复杂度

5,sort依赖的序关系

三,C++ sort  vs  C qsort

1,严格弱序的测试

(1)sort

(2)qsort

2,非严格弱序的测试

(1)sort

(2)qsort

3,真实全等数组的测试

(1)sort

(2)qsort

4,编译器优化

四,sort的稳定性测试

1,随机测试

2,在线测试

五,拓展排序

1,拓展成稳定排序

2,拓展排序和原排序

六,OJ实战

力扣 1451. 重新排列句子中的单词(稳定排序)

CSU 1215 稳定排序(稳定排序)

力扣 2512. 奖励最顶尖的 K 名学生(双关键字排序)


一,快速排序

1,原理

快速排序是一种分治算法,每次选择1个数x,把小于x的和大于x的数分别放在2边。

2,不稳定快速排序

代码参考常见排序算法


template<typename T>
bool cmp(T a, T b)
{
	return a < b;
}

template<typename T>
void Sort(T* arr, int low, int high, bool(*cmp)(T a, T b))
{
	if (low >= high)return;
	T x = arr[high];
	int id = high;
	for (int i = low; i < id;) {
		if (cmp(arr[i], x))i++;
		else exchange(arr + i, arr + id--);
	}
	arr[id] = x;
	Sort(arr, low, id - 1,cmp);
	Sort(arr, id + 1, high, cmp);
}
template<typename T>
void Sort(T* arr, int len, bool(*cmp)(T a, T b))
{
	Sort(arr, 0, len - 1, cmp);
}

template<typename T>
void Sort(T* arr, int len)
{
	Sort(arr, len, cmp);
}

3,稳定快速排序

一般的快速排序都是不稳定排序,每个人写快速排序写的可能都不太一样,根据具体代码很容易找到是不稳定排序的证据。

如果快速排序改一改,可以做到稳定排序,不过这只能算快速排序的修改版,因为不够快,一般说的快速排序指的就是不稳定的快速排序。


template<typename T>
bool cmp(T a, T b)
{
	return a < b;
}

template<typename T>
void Sort(T* arr, T* arr2, int low, int high, bool(*cmp)(T a, T b))
{
	if (low >= high)return;
	int id = low, id2, id3;
	T x = arr[high];
	for (int i = low; i < high; i++) {
		if (cmp(arr[i], arr[high]))arr2[id++] = arr[i];
	}
	id2 = id;
	for (int i = low; i < high; i++) {
		if (!cmp(arr[i], arr[high]) && !cmp(arr[high], arr[i]))arr2[id++] = arr[i];
	}
	arr2[id++] = x;
	id3 = id;
	for (int i = low; i < high; i++) {
		if (cmp(arr[high], arr[i]))arr2[id++] = arr[i];
	}
	for (int i = low; i <= high; i++)arr[i] = arr2[i];
	Sort(arr, arr2, low, id2 - 1, cmp);
	Sort(arr, arr2, id3, high, cmp);
}
template<typename T>
void Sort(T* arr, int len, bool(*cmp)(T a, T b))
{
	T* arr2 = new T[len];
	Sort(arr, arr2, 0, len - 1, cmp);
}

template<typename T>
void Sort(T* arr, int len)
{
	Sort(arr, len, cmp);
}

4,弱序关系和严格弱序关系

我们经常自定义排序函数,基本上都会写成2种情况,要么就是全序,要么就是严格弱序。

如果是在STL里面使用自定义排序函数,比如 sort 函数或者 priority_queue 里面,就必须使用严格弱序,也就是说,比较2个相同的元素时必须return false

(1)弱序、严格弱序

二元关系

(2)序关系最小集

严格弱序关系需要定义{<},全序需要定义{<=}

<可以推导>,再推导==、<=、>=、!=等等

<=也是类似的。

(3)快速排序依赖的序关系

每个人写快速排序写的可能都不太一样,有的只支持严格弱序,即只支持小于函数,有的都支持,即支持小于函数和小于等于函数。

上面的2个代码刚好对应了2种情况。

5,快速排序总结

虽然稳定性和是否要求必须是严格弱序,关键都在于算法对于等序数据如何处理,但实际上写法还是太多了,所以不用深究,只要是快速算法,我们一律认为是不稳定排序且必须是严格弱序即可。

下文的测试情况,能更好的说明这一点。

二,C++ sort

C++中的 sort是快速排序、堆排序、插入排序三者的结合,其中快速排序、堆排序是不稳定排序,插入排序是稳定排序。

1,sort使用

sort函数有2种常见的写法,一种是不带参的,也就是默认的升序,一种是传入函数指针或仿函数的,用函数自定义排序规则。

示例:

#include<iostream>
#include<algorithm>
#include<functional>
using namespace std;

class cmp
{
public:
	bool operator()(int a, int b)  //从小到大
	{
		return a < b;
	}
};

bool cmp2(int a, int b)  //从大到小
{
	return a > b;
}

int main()
{
	int num[4] = { 1, 3, 4, 2 };
	sort(num, num + 4);
	cout << num[0] << num[1] << num[2] << num[3] << endl;
	sort(num, num + 4, cmp2);
	cout << num[0] << num[1] << num[2] << num[3] << endl;
	sort(num, num + 4, cmp());
	cout << num[0] << num[1] << num[2] << num[3] << endl;
	sort(num, num + 4, greater<>());
	cout << num[0] << num[1] << num[2] << num[3] << endl;
	sort(num, num + 4, less<>());
	cout << num[0] << num[1] << num[2] << num[3] << endl;
	return 0;
}

输出:

1234
4321
1234
4321
1234

其中,sort(num, num + 4);是默认的升序排序,

sort(num, num + 4, cmp2);  是传入cmp2这个函数指针,

而sort(num, num + 4, c); 是传入一个对象,对象中重载了()运算符。

greater<>() 和 less<>() 都是头文件<functional>中的仿函数,里面封装了>和<运算符。

2,sort源码

 C++中sort的实现代码:

template<class _RanIt,
	class _Pr> inline
	void sort(_RanIt _First, _RanIt _Last, _Pr _Pred)
	{	// order [_First, _Last), using _Pred
	_DEBUG_RANGE(_First, _Last);
	_DEBUG_POINTER(_Pred);
	_Sort(_Unchecked(_First), _Unchecked(_Last), _Last - _First, _Pred);
	}

		// TEMPLATE FUNCTION sort
template<class _RanIt> inline
	void sort(_RanIt _First, _RanIt _Last)
	{	// order [_First, _Last), using operator<
	_STD sort(_First, _Last, less<>());
	}

可以看出,默认的比较函数就是less<>()

再附上其中_Sort函数的代码:

template<class _RanIt,
	class _Diff,
	class _Pr> inline
	void _Sort(_RanIt _First, _RanIt _Last, _Diff _Ideal, _Pr _Pred)
	{	// order [_First, _Last), using _Pred
	_Diff _Count;
	for (; _ISORT_MAX < (_Count = _Last - _First) && 0 < _Ideal; )
		{	// divide and conquer by quicksort
		pair<_RanIt, _RanIt> _Mid =
			_Unguarded_partition(_First, _Last, _Pred);
		_Ideal /= 2, _Ideal += _Ideal / 2;	// allow 1.5 log2(N) divisions

		if (_Mid.first - _First < _Last - _Mid.second)
			{	// loop on second half
			_Sort(_First, _Mid.first, _Ideal, _Pred);
			_First = _Mid.second;
			}
		else
			{	// loop on first half
			_Sort(_Mid.second, _Last, _Ideal, _Pred);
			_Last = _Mid.first;
			}
		}

	if (_ISORT_MAX < _Count)
		{	// heap sort if too many divisions
		_STD make_heap(_First, _Last, _Pred);
		_STD sort_heap(_First, _Last, _Pred);
		}
	else if (1 < _Count)
		_Insertion_sort(_First, _Last, _Pred);	// small
	}

3,sort原理

从源码可以看出是快速排序、堆排序、插入排序三者的结合。

3个排序的结合方法:

(1)深度太深时使用堆排序

_Ideal /= 2, _Ideal += _Ideal / 2;    // allow 1.5 log2(N) divisions

这个注释不对,实际上应该是log(N)/log(4/3)次划分,大概等于2.4 log2(N)

_Ideal 这个量没有具体的含义,它既是一开始等于N,然后每次乘以3/4,当它变为0时我就当做深度太深,剩下的交给堆排序

(2)元素太少时使用插入排序

这里的常量_ISORT_MAX = 32,即当递归到只有32个元素时,对这个小片段采取插入排序。

片段花费时间32*31

如果N个元素分割成N/32个片段,每个片段都是32个元素,都采取插入排序,那么总时间是:

N/32 * 32*31 + N* log2(N/32) = N* (log2(N) +26)

这个结果可以认为就是N* log2(N)

4,时间复杂度

快速排序的优点是平均时间比堆排序快,但时间复杂度是O(n^2),而堆排序的时间复杂度是O(n  log n)

这里的sort函数,平均排序时间非常快,而且时间复杂度是O(n log n)

5,sort依赖的序关系

不同版本的STL中,sort写的也不一样,有的只支持严格弱序,即只支持小于函数,有的都支持,即支持小于函数和小于等于函数。

三,C++ sort  vs  C qsort

1,严格弱序的测试

我们模拟所有数据都相等的情况,直接给cmp函数进行打桩。

(1)sort

int cmp(int a, int b)
{
    return 0;
}

#define p 2
int ns[p];

int main()
{    
    clock_t start, stop;
    start=clock();
    sort(ns,ns+p,cmp);
    stop=clock();
    cout<<(double)(stop-start);
    return 0;
}

12345 1
123456  1
1234567 5
12345678 50
123456789 487

release模式下,排序10^8个数大概需要半秒

(2)qsort

int cmp(const void *a, const void *b)
{
    return 0;
}

#define p 12345678
int ns[p];

int main()
{  
    clock_t start, stop;
    start=clock();
    qsort(ns,p,sizeof(int),cmp);
    stop=clock();
    cout<<(double)(stop-start);
    return 0;
}

12345 1
123456 1
1234567 5
12345678 64
123456789 601

该测试场景下,qsort比sort略慢一点。

2,非严格弱序的测试

把cmp函数的打桩都改成return 1;,变成非严格弱序

(1)sort

某个版本的sort,哪怕p=2,只有2个数,程序也会直接崩溃,因为这不是严格弱序。

另外一个版本的sort:

12345   0
123456  5
1234567 50
12345678 590
123456789 5754

耗时比严格弱序的情况高很多

(2)qsort

12345   91
123456  8966

可以看出,qsort对于非严格弱序虽然可以完成排序,但是非常慢,比sort慢很多很多。

3,真实全等数组的测试

(1)sort

int cmp(int a, int b)
{
    return a>b;
}

#define p 123456
int ns[p];

int main()
{  
    for(int i=0;i<p;i++)ns[i]=0;
    clock_t start, stop;
    start=clock();
    sort(ns,ns+p,cmp);
    stop=clock();
    cout<<(double)(stop-start);
    return 0;
}

12345 0
123456  0
1234567 3
12345678 36
123456789 357

(2)qsort

int cmp(const void *a, const void *b)
{
    return *(int *)a > *(int *)b;
}

#define p 12345
int ns[p];

int main()
{  
    for(int i=0;i<p;i++)ns[i]=0;
    clock_t start, stop;
    start=clock();
    qsort(ns,p,sizeof(int),cmp);
    stop=clock();
    cout<<(double)(stop-start);
    return 0;
}

12345 0
123456  0
1234567 7
12345678 73
123456789 625

可以看出,打桩测试的结果和真实数组是一致的。

这里的2个代码是严格弱序的正常代码,如果把cmp里面的>改成>=那就变成了非严格弱序,测试情况也是一致的。

4,编译器优化

可以看出,优化后sort的性能远远高于qsort

四,sort的稳定性测试

1,随机测试

利用排序稳定性测试函数,很容易找到sort是不稳定排序的例子:

667 679 421 826 516 666 681 340 21 721 309 902 124 745 423 806 318 907 481 136 114 84 290 996 54 708 491 131 439 722 995 269 238 918 659 486 462 158 146 37 647 602 98 292 278 352 882 315 762 336 418 828 816 449 658 520 560 162 675 361 398 714 188 638 75 521 615 234 905 557 161 549 330 350 925 737 337 393 714 591 135 337 337 664 608 281 152 652 876 396 249 174 943 941 419 805 216 615 64 893
 

2,在线测试

(1)在线测试

(下文的)力扣 1451. 重新排列句子中的单词 的题意是根据字符串的长度排序,而且要稳定排序,于是也可以用来寻找sort不稳定的例子。

其中我的比较函数是

bool cmp(nodes x, nodes y)
{
    if(x.s.length()==y.s.length())return x.k<y.k;
    return x.s.length()<y.s.length();
}

只要把 if(x.s.length()==y.s.length())return x.k<y.k; 这句话去掉再重新提交就能知道sort是不是稳定排序了。

提交之后挂了,于是我得到了这个用例

"Jlhvvd wfwnphmxoa qcuucx qsvqskq cqwfypww dyphntfz hkbwx xmwohi qvzegb ubogo sbdfmnyeim tuqppyipb llwzeug hrsaebveez aszqnvruhr xqpqd ipwbapd mlghuuwvec xpefyglstj dkvhhgecd kry"

把它转化为长度数组:6 10 6 7 8 8 5 6 6 5 10 9 7 10 10 5 7 10 10 9 3

利用我封装的sort输出ID的程序;

//给vector拓展,加上id并排序
template<typename T>
bool cmp(T x,T y)
{
    return x<y;
}
template<typename T>
vector<pair<T,int>> sortWithId(vector<T>v)
{
    vector<pair<T,int>>ans;
    ans.resize(v.size());
    for(int i=0;i<v.size();i++)ans[i].first=v[i],ans[i].second=i;
    sort(ans.begin(),ans.end(),[](pair<T,int>p1,pair<T,int>p2){return cmp(p1.first,p2.first);});
    return ans;      
}

我输出了这个数组的排序ID:

看起来没毛病啊!

于是我把这个题目代码放本地运行:

结果是对的啊!

同一份代码,在OJ和本地运行结果不一样,应该是STL版本不同导致的。

(2)sort重写

为了利用OJ验证我本地的sort函数是不是稳定排序,我需要用我本地的sort函数替代OJ里面的sort函数。

经过简单的处理,我得到了这样的代码:

#define _STD	::std::
const int _ISORT_MAX = 32;
  #define _DEBUG_LT_PRED(pred, x, y)	pred(x, y)

template<class _BidIt,
	class _Pr> inline
	void _Insertion_sort(_BidIt _First, _BidIt _Last, _Pr _Pred)
	{	// insertion sort [_First, _Last), using _Pred
	for(auto i=_First;i!=_Last;i++)for(auto j=i+1;j!=_Last;j++)
    	if (!_DEBUG_LT_PRED(_Pred, *i, *j))
		_STD iter_swap(i, j);
	}
template<class _RanIt,
	class _Pr> inline
	void _Med3(_RanIt _First, _RanIt _Mid, _RanIt _Last, _Pr _Pred)
	{	// sort median of three elements to middle
	if (_DEBUG_LT_PRED(_Pred, *_Mid, *_First))
		_STD iter_swap(_Mid, _First);
	if (_DEBUG_LT_PRED(_Pred, *_Last, *_Mid))
		_STD iter_swap(_Last, _Mid);
	if (_DEBUG_LT_PRED(_Pred, *_Mid, *_First))
		_STD iter_swap(_Mid, _First);
	}

template<class _RanIt,
	class _Pr> inline
	void _Median(_RanIt _First, _RanIt _Mid, _RanIt _Last, _Pr _Pred)
	{	// sort median element to middle
	if (40 < _Last - _First)
		{	// median of nine
		size_t _Step = (_Last - _First + 1) / 8;
		_Med3(_First, _First + _Step, _First + 2 * _Step, _Pred);
		_Med3(_Mid - _Step, _Mid, _Mid + _Step, _Pred);
		_Med3(_Last - 2 * _Step, _Last - _Step, _Last, _Pred);
		_Med3(_First + _Step, _Mid, _Last - _Step, _Pred);
		}
	else
		_Med3(_First, _Mid, _Last, _Pred);
	}

template<class _RanIt,
	class _Pr> inline
	_STD pair<_RanIt, _RanIt>
		_Unguarded_partition(_RanIt _First, _RanIt _Last, _Pr _Pred)
	{	// partition [_First, _Last), using _Pred
	_RanIt _Mid = _First + (_Last - _First) / 2;
	_Median(_First, _Mid, _Last - 1, _Pred);
	_RanIt _Pfirst = _Mid;
	_RanIt _Plast = _Pfirst + 1;

	while (_First < _Pfirst
		&& !_DEBUG_LT_PRED(_Pred, *(_Pfirst - 1), *_Pfirst)
		&& !_Pred(*_Pfirst, *(_Pfirst - 1)))
		--_Pfirst;
	while (_Plast < _Last
		&& !_DEBUG_LT_PRED(_Pred, *_Plast, *_Pfirst)
		&& !_Pred(*_Pfirst, *_Plast))
		++_Plast;

	_RanIt _Gfirst = _Plast;
	_RanIt _Glast = _Pfirst;

	for (; ; )
		{	// partition
		for (; _Gfirst < _Last; ++_Gfirst)
			if (_DEBUG_LT_PRED(_Pred, *_Pfirst, *_Gfirst))
				;
			else if (_Pred(*_Gfirst, *_Pfirst))
				break;
			else
				_STD iter_swap(_Plast++, _Gfirst);
		for (; _First < _Glast; --_Glast)
			if (_DEBUG_LT_PRED(_Pred, *(_Glast - 1), *_Pfirst))
				;
			else if (_Pred(*_Pfirst, *(_Glast - 1)))
				break;
			else
				_STD iter_swap(--_Pfirst, _Glast - 1);
		if (_Glast == _First && _Gfirst == _Last)
			return (_STD pair<_RanIt, _RanIt>(_Pfirst, _Plast));

		if (_Glast == _First)
			{	// no room at bottom, rotate pivot upward
			if (_Plast != _Gfirst)
				_STD iter_swap(_Pfirst, _Plast);
			++_Plast;
			_STD iter_swap(_Pfirst++, _Gfirst++);
			}
		else if (_Gfirst == _Last)
			{	// no room at top, rotate pivot downward
			if (--_Glast != --_Pfirst)
				_STD iter_swap(_Glast, _Pfirst);
			_STD iter_swap(_Pfirst, --_Plast);
			}
		else
			_STD iter_swap(_Gfirst++, --_Glast);
		}
	}

template<class _RanIt,
	class _Diff,
	class _Pr> inline
	void _Sort2(_RanIt _First, _RanIt _Last, _Diff _Ideal, _Pr _Pred)
	{	// order [_First, _Last), using _Pred
	_Diff _Count;
	for (; _ISORT_MAX < (_Count = _Last - _First) && 0 < _Ideal; )
		{	// divide and conquer by quicksort
		_STD pair<_RanIt, _RanIt> _Mid =
			_Unguarded_partition(_First, _Last, _Pred);
		_Ideal /= 2, _Ideal += _Ideal / 2;	// allow 1.5 log2(N) divisions

		if (_Mid.first - _First < _Last - _Mid.second)
			{	// loop on second half
			_Sort2(_First, _Mid.first, _Ideal, _Pred);
			_First = _Mid.second;
			}
		else
			{	// loop on first half
			_Sort2(_Mid.second, _Last, _Ideal, _Pred);
			_Last = _Mid.first;
			}
		}

	if (_ISORT_MAX < _Count)
		{	// heap sort if too many divisions
		_STD make_heap(_First, _Last, _Pred);
		_STD sort_heap(_First, _Last, _Pred);
		}
	else if (1 < _Count)
		_Insertion_sort(_First, _Last, _Pred);	// small
	}

template<class _RanIt,
	class _Pr> inline
	void sort2(_RanIt _First, _RanIt _Last, _Pr _Pred)
	{	// order [_First, _Last), using _Pred
	_Sort2((_First), (_Last), _Last - _First, _Pred);
	}

其中_Insertion_sort函数是我自己写的,其他是从我本地IDE移植过来的。

PS:原谅我写了一个选择排序

把题目里面的sort(v.begin(),v.end(),cmp);改成sort2(v.begin(),v.end(),cmp);提交,发现可以通过。

再把cmp比较函数里面的if(x.s.length()==y.s.length())return x.k<y.k;这句话注释掉,发现解答错误。

这是我们需要的sort不稳定的用例吗?显然不是!

(3)稳定分支控制

sort是快速排序、堆排序、插入排序三者的结合,其中快速排序、堆排序是不稳定排序,插入排序是稳定排序。

而被我改写之后,就变成的快速排序、堆排序、我的选择排序,选择排序是不稳定排序。

其中堆排序的分支在数据量很小时是调用不到的,所以我又把_Insertion_sort改写成了冒泡排序,这个是稳定排序:

template<class _BidIt,
	class _Pr> inline
	void _Insertion_sort(_BidIt _First, _BidIt _Last, _Pr _Pred)
	{	// insertion sort [_First, _Last), using _Pred
	for(auto i=_First;i!=_Last;i++)for(auto j=_First;j-_First<_Last-i-1;j++)
    	if (_DEBUG_LT_PRED(_Pred, *(j+1), *j))
		_STD iter_swap(j, (j+1));
	}

再次提交,我得到了这个用例:

Npwfcxhzs diftsrym vzkdo uxwlha eyocyxo vdsdiyryer xqqghawjp rnpowjs wmqerrs egrriqsivq rvtlymg paecqitg klukaw otyerkmmnk chyenhxez trpge iqfcxax qwwpz amcnilipmj xwespxz jqzqbs riytunnotn ktbbursmxl xyqhan jitujj etkxlxdmx csljl zbbyu vxyntzvpa qrtufudlap hwhvjcjv yqfzij jlhet vuwiyf wozdclg xqegx kwyvqfwve lkchg oxxzctzolz efwrp xflitpgxl gwpiomzlh mcirgs uwkcquzu lxqtbe emyjgor fszjyon xwnopy gzni

把它长度提取出来:

9 8 5 6 7 10 9 7 7 10 7 8 6 10 9 5 7 5 10 7 6 10 10 6 6 9 5 5 9 10 8 6 5 6 7 5 9 5 10 5 9 9 6 8 6 7 7 6 4

还是用我的程序,调用sort并输出ID:

经过打断点确认了没有调用到堆排序,插入排序也是稳定的,所以这就是sort函数不稳定的用例。

准确的说,是sort函数里面的快速排序不稳定的用例。

五,拓展排序

1,拓展成稳定排序

通过把数据进行拓展,并把比较函数改成双关键字比较,可以得到一个利用任意不稳定排序得到稳定排序的代码。

参考这里的VectorOpt

2,拓展排序和原排序

拓展排序和原排序的结果一定一致吗?

排序结果一致的条件有2个:

(1)排序算法不含随机行为,即无论调用多少次,它的行为都是完全一致的。

(2)比较函数对拓展封闭,即拓展后的比较逻辑和原比较逻辑完全一致。

所以,基于一个不含随机行为的不稳定排序进行拓展,有2种情况

(1)比较逻辑(即cmp函数)不变,即只比较原数据,则拓展排序和原排序是一致的。

(2)比较逻辑改成双关键字排序,得到稳定排序,和原来的不稳定排序的结果是不一致的。

上面的findSum 中两次用到sort函数:

vector<int>v3 = sortId(v2);
sort(v2.begin(), v2.end(), cmp<T>);

这2个排序的结果其实是不一致的,但是这个地方的用法仍然是对的。

六,OJ实战

力扣 1451. 重新排列句子中的单词(稳定排序)

「句子」是一个用空格分隔单词的字符串。给你一个满足下述格式的句子 text :

  • 句子的首字母大写
  • text 中的每个单词都用单个空格分隔。

请你重新排列 text 中的单词,使所有单词按其长度的升序排列。如果两个单词的长度相同,则保留其在原句子中的相对顺序。

请同样按上述格式返回新的句子。

示例 1:

输入:text = "Leetcode is cool"
输出:"Is cool leetcode"
解释:句子中共有 3 个单词,长度为 8 的 "Leetcode" ,长度为 2 的 "is" 以及长度为 4 的 "cool" 。
输出需要按单词的长度升序排列,新句子中的第一个单词首字母需要大写。

示例 2:

输入:text = "Keep calm and code on"
输出:"On and keep calm code"
解释:输出的排序情况如下:
"On" 2 个字母。
"and" 3 个字母。
"keep" 4 个字母,因为存在长度相同的其他单词,所以它们之间需要保留在原句子中的相对顺序。
"calm" 4 个字母。
"code" 4 个字母。

示例 3:

输入:text = "To be or not to be"
输出:"To be or to be not"

提示:

  • text 以大写字母开头,然后包含若干小写字母以及单词间的单个空格。
  • 1 <= text.length <= 10^5

思路一:

手动实现拓展排序

struct nodes
{
    string s;
    int k;
};
 
bool cmp(nodes x, nodes y)
{
    if(x.s.length()==y.s.length())return x.k<y.k;
    return x.s.length()<y.s.length();
}
 
class Solution {
public:
    string arrangeWords(string text) {
        text[0]+='a'-'A';
        vector<nodes>v;
        v.clear();
        int low=0,key=0;
        for(int i=0;i<=text.length();i++)
        {
            if(i==text.length() || text[i]==' ')
            {
                nodes nod;
                nod.s=text.substr(low,i-low);
                nod.k=key++;
                v.insert(v.end(),nod);
                low=i+1;
            }
        }
        sort(v.begin(),v.end(),cmp);
        string ans=v[0].s;
        for(int i=1;i<v.size();i++)
        {
            ans+=" ";
            ans+=v[i].s;
        }
        ans[0]+='A'-'a';
        return ans;
    }
};

思路二:

利用模板

class Solution {
public:
	string arrangeWords(string text) {
		text[0] += 'a' - 'A';
		vector<string>v = DrawFirst(SortWithId(StringSplit(text)));
		string ans = v[0];
		for (int i = 1; i < v.size(); i++)ans += " " + v[i];
		ans[0] += 'A' - 'a';
		return ans;
	}
};

CSU 1215 稳定排序(稳定排序)

 题目:
Description

给出二元数组a[MAXN][2],按第一个关键值从小到大排序后输出,要求第一关键值相同情况下不改变原数组次序

Input

每组数据第一行为整数n,1 <= n <= 10 ^ 5。

接下来n行每行两个整数空格隔开。

Output

输出排序后的数组

Sample Input

3
2 4
1 0
2 3
3
4 2
0 4
0 2
Sample Output

1 0
2 4
2 3
0 4
0 2
4 2

这个题目需要稳定的排序,而且n还比较大,必须是归并排序这种可以在O(n log n)时间内排序的算法。

代码:
 

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

struct node
{
	int key;
	int b;
};

node list[100001];
node list1[100001];

void merge(node *s, node *t, int i, int m, int n)
{
	int k = i;
	int j = m + 1;
	for (; i <= m && j <= n; ++k)
	{
		if (s[i].key <= s[j].key)t[k] = s[i++];
		else t[k] = s[j++];
	}
	if (i <= m)for (int ii = k, jj = i; ii <= n; ii++, jj++)t[ii] = s[jj];
	if (j <= n)for (int ii = k, jj = j; ii <= n; ii++, jj++)t[ii] = s[jj];
}

void msort(node *sr, node *tr1, int s, int t, int n)
{
	if (s == t)tr1[s] = sr[s];
	else
	{
		node *list = new node[n];
		int m = (s + t) / 2;
		msort(sr, list, s, m, n);
		msort(sr, list, m + 1, t, n);
		merge(list, tr1, s, m, t);
		delete list;
	}
}

int main()
{
	int n;
	while (cin >> n)
	{
		for (int i = 0; i < n; i++)
		{
			scanf("%d", &list[i].key);
			scanf("%d", &list[i].b);
		}
		msort(list, list1, 0, n - 1, n);
		for (int i = 0; i<n; i++)printf("%d %d\n", list1[i].key, list1[i].b);
	}
	return 0;
}

这个题目也可以用sort,但是sort是不稳定排序,要想实现稳定排序需要扩充数据。

下面3个///标记的地方就是我对汤学弟的代码进行修改的3个地方,扩充了结构体才能变成稳定排序

#include<iostream>
#include<algorithm>
using namespace std;
struct num{
	long data1;
	long data2;
	long key;//
}Num[100005];
bool cmp(num a, num b)
{
	if (a.data1 == b.data1)
		return a.key < b.key;//
	else
		return a.data1<b.data1;
}
int main()
{
	ios::sync_with_stdio(false);
	int n;
	while (cin >> n)
	{
		for (int i = 0; i < n; i++)
			cin >> Num[i].data1 >> Num[i].data2, Num[i].key = i;///
		sort(Num, Num + n, cmp);
		for (int i = 0; i<n; i++)
			cout << Num[i].data1 << " " << Num[i].data2 << endl;
	}
	return 0;
}

力扣 2512. 奖励最顶尖的 K 名学生(双关键字排序)

给你两个字符串数组 positive_feedback 和 negative_feedback ,分别包含表示正面的和负面的词汇。不会 有单词同时是正面的和负面的。

一开始,每位学生分数为 0 。每个正面的单词会给学生的分数 加 3 分,每个负面的词会给学生的分数 减  1 分。

给你 n 个学生的评语,用一个下标从 0 开始的字符串数组 report 和一个下标从 0 开始的整数数组 student_id 表示,其中 student_id[i] 表示这名学生的 ID ,这名学生的评语是 report[i] 。每名学生的 ID 互不相同

给你一个整数 k ,请你返回按照得分 从高到低 最顶尖的 k 名学生。如果有多名学生分数相同,ID 越小排名越前。

示例 1:

输入:positive_feedback = ["smart","brilliant","studious"], negative_feedback = ["not"], report = ["this student is studious","the student is smart"], student_id = [1,2], k = 2
输出:[1,2]
解释:
两名学生都有 1 个正面词汇,都得到 3 分,学生 1 的 ID 更小所以排名更前。

示例 2:

输入:positive_feedback = ["smart","brilliant","studious"], negative_feedback = ["not"], report = ["this student is not studious","the student is smart"], student_id = [1,2], k = 2
输出:[2,1]
解释:
- ID 为 1 的学生有 1 个正面词汇和 1 个负面词汇,所以得分为 3-1=2 分。
- ID 为 2 的学生有 1 个正面词汇,得分为 3 分。
学生 2 分数更高,所以返回 [2,1] 。

提示:

  • 1 <= positive_feedback.length, negative_feedback.length <= 104
  • 1 <= positive_feedback[i].length, negative_feedback[j].length <= 100
  • positive_feedback[i] 和 negative_feedback[j] 都只包含小写英文字母。
  • positive_feedback 和 negative_feedback 中不会有相同单词。
  • n == report.length == student_id.length
  • 1 <= n <= 104
  • report[i] 只包含小写英文字母和空格 ' ' 。
  • report[i] 中连续单词之间有单个空格隔开。
  • 1 <= report[i].length <= 100
  • 1 <= student_id[i] <= 109
  • student_id[i] 的值 互不相同 。
  • 1 <= k <= n
class Solution {
public:
	vector<int> topStudents(vector<string>& positive_feedback, vector<string>& negative_feedback, vector<string>& report, vector<int>& student_id, int k) {
		vector<int>ans(student_id.size());
		set<string>s1, s2;
		for (auto s : positive_feedback)s1.insert(s);
		for (auto s : negative_feedback)s2.insert(s);
		for (int i = 0; i < ans.size(); i++) {
			ans[i] = 0;
			auto v = StringSplit(report[i]);
			for (auto s : v) {
				if (s1.find(s) != s1.end())ans[i] -= 3;
				else if (s2.find(s) != s2.end())ans[i] += 1;
			}
		}

		SortExtend(student_id, ans);
		auto ids = SortId(ans);
		vector<int>res;
		for (int i = 0; i < k; i++) {
			res.push_back(student_id[ids[i]]);
		}
		return res;
	}
};

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
快速排序(Quick Sort)是一种常用的排序算法,它的基本思想是通过递归地将数组分成两个子数组,再对这两个子数组进行排序,最终将整个数组排序。 具体步骤如下: 1. 选取一个基准元素(pivot),通常选择第一个或最后一个元素。 2. 将数组分成两部分,使得左边的元素都小于等于基准元素,右边的元素都大于基准元素。 3. 递归地对左右两部分进行排序。 4. 合并左右两部分,得到最终的排序结果。 以下是 C 语言实现快速排序qsort 函数的示例代码: ```c #include <stdio.h> void swap(int* a, int* b) { int temp = *a; *a = *b; *b = temp; } int partition(int arr[], int low, int high) { int pivot = arr[high]; int i = (low - 1); for (int j = low; j <= high - 1; j++) { if (arr[j] < pivot) { i++; swap(&arr[i], &arr[j]); } } swap(&arr[i + 1], &arr[high]); return (i + 1); } void quickSort(int arr[], int low, int high) { if (low < high) { int pi = partition(arr, low, high); quickSort(arr, low, pi - 1); quickSort(arr, pi + 1, high); } } int main() { int arr[] = { 10, 7, 8, 9, 1, 5 }; int n = sizeof(arr) / sizeof(arr[0]); quickSort(arr, 0, n - 1); printf("Sorted array: "); for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } return 0; } ``` 这段代码实现了一个快速排序算法,将数组 [10, 7, 8, 9, 1, 5] 进行排序,并输出结果。你可以根据自己的需求修改数组元素和大小,然后运行代码进行测试。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值