之前我们用到的排序的时间复杂度都是O(n^2)的,并不乐观,但是我们依然有更快的算法,它就是快速排序,时间复杂度O(n log n)。
我们还是先看看快速排序的原理:
可能这个排序有些人看不懂,就让我们来讲一下它的基本原理吧。
比如我们来排序6 1 2 7 9 3 4 5 10 8
方法其实很简单:分别从初始序列“6 1 2 7 9 3 4 5 10 8”两端开始“探测”。先从右往左找一个小于6的数,再从左往右找一个大于6的数,然后交换他们。这里可以用两个变量i和j,分别指向序列最左边和最右边。我们把这两个变量叫i和j。刚开始的时候让i指向序列的最左边(即i=1),指向数字6。让j指向序列的最右边(即=10),指向数字。
然后j开始移动。因为此处设置的基准数是最左边的数,所以需要让j先移动,这一点非常重要(请自己想一想为什么)。j一步一步地向左挪动(即j--),直到找到一个小于6的数停下来。接下来i再一步一步向右挪动(即i++),直到找到一个数大于6的数停下来。最后j停在了数字5面前,i停在了数字7面前。
现在交换哨兵i和哨兵j所指向的元素的值。交换之后的序列如下:
6 1 2 5 9 3 4 7 10 8
到此,第一次交换结束。接下来开始j继续向左挪动(每次必须是j先出发)。他发现了4(比基准数6要小,满足要求)之后停了下来。i也继续向右挪动的,他发现了9(比基准数6要大,满足要求)之后停了下来。此时再次进行交换,交换之后的序列如下:
6 1 2 5 4 3 9 7 10 8
第二次交换结束,扫描继续。j继续向左挪动,他发现了3(比基准数6要小,满足要求)之后又停了下来。i继续向右移动,此时i和j相遇了,i和j都走到3面前。说明此时扫描结束。我们将基准数6和3进行交换。交换之后的序列如下:
3 1 2 5 4 6 9 7 10 8
到此第一轮扫描真正结束。此时以基准数6为分界点,6左边的数都小于等于6,6右边的数都大于等于6。回顾一下刚才的过程,其实j就是要找小于基准数的数,而i就是要找大于基准数的数,直到i和j碰头为止。
之后,不断重复这个动作,将范围不断缩小,最终成为有序序列。
我们不妨来看看代码。
int a[101],n;
void quicksort(int left,int right)
{
int i,j,t,temp;
if(left>right)
return;
temp=a[left]; //temp中存的就是基准数
i=left;
j=right;
while(i!=j)
{
//顺序很重要,要先从右边开始找
while(a[j]>=temp && i<j) j--;
while(a[i]<=temp && i<j) i++;
//交换两个数
if(i<j)
{
t=a[i];
a[i]=a[j];
a[j]=t;
}
}
//最终将基准数归位
a[left]=a[i];
a[i]=temp;
quicksort(left,i-1);//处理左边的
quicksort(i+1,right);//处理右边的
//这里是一个递归的过程 ,就是不断缩小范围,最终得解
}
这就是快排。我们想一想为什么他会“快”。
快速排序之所比较快,因为相比冒泡排序,每次交换是跳跃式的。每次排序的时候设置一个基准点,将小于等于基准点的数全部放到基准点的左边,将大于等于基准点的数全部放到基准点的右边。这样在每次交换的时候就不会像冒泡排序一样每次只能在相邻的数之间进行交换,交换的距离就大的多了。因此总的比较和交换次数就少了,速度自然就提高了。当然在最坏的情况下,仍可能是相邻的两个数进行了交换。因此快速排序的最差时间复杂度和冒泡排序是一样的都是O(n^2),它的平均时间复杂度为O(n log n)。其实快速排序是基于一种叫做“二分”的思想。至于二分思想,我们以后会介绍。
手写快排会了吧?现在,关上门,我们单独说话。
有一个神器,叫sort,也就是排序的意思,他能干什么呢?
是的,他可以排序。
来看看两个人的代码:
1.
#include<iostream>
using namespace std;
int a[101],n;
void quicksort(int left,int right)
{
int i,j,t,temp;
if(left>right)
return;
temp=a[left]; //temp中存的就是基准数
i=left;
j=right;
while(i!=j)
{
//顺序很重要,要先从右边开始找
while(a[j]>=temp && i<j) j--;
while(a[i]<=temp && i<j) i++;
//交换两个数
if(i<j)
{
t=a[i];
a[i]=a[j];
a[j]=t;
}
}
//最终将基准数归位
a[left]=a[i];
a[i]=temp;
quicksort(left,i-1);//处理左边的
quicksort(i+1,right);//处理右边的
//这里是一个递归的过程 ,就是不断缩小范围,最终得解
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
quicksort(1,n);
for(int i=1;i<=n;i++)
cout<<a[i]<<endl;
return 0;
}
这是你
2.
#include<iostream>
#include<algorithm>
using namespace std;
int n,a[10001];
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
sort(a+1,a+n+1);
for(int i=1;i<=n;i++)
cout<<a[i]<<endl;
return 0;
}
这是大佬
OhhhhOhhhhhOhhhOhhhh
那么,大佬用到了sort,他是排序,包含在algorithm(算法)库中,有着极大的功能。
与我们的quicksort不同的是,sort函数的两个参数是由 一个字符“+”一个数字,就像 a+1,a+n+1一样。我们看到,a是我们待排序数组的数组名,1和n+1是待排序数组的元素编号,注意,这里是下表从0开始,我们用a[1]存,就要写a+1,我们用a[n+1],就要写a+n+1。怎么样,不错吧?
不过,我们测试之后发现,sort是默认升序排序,也就是从小到大,那如果我们要求从大到小降序排列怎么办呢?
没事,C++给了我们一个极大的权力:控制sort的排序规则。我们来看看如何做到。
bool cmp(int a,int b)//这里最好是bool
{
return a>b;
}
int main()
{
sort(a+1,a+n+1,cmp/*多出来的参数*/);
}
我们用bool型制造了一个函数:cmp,而sort中就加了一个参数:cmp。不错,这就是我们的规则。先让我们来看看为什么我们要用bool作为类型。
return a>b; 若a<=b,则返回0,sort的规则是,你制定的规则在一组数据中返回了0,就交换,才导致最后的序列是降序。其实,用int也行,只是bool的逻辑关系更明确。
如果你理解不了上面那一句话,那就看看通俗的讲解吧:
return a>b就是使所有的(a,b)[a在b前面] 使a>b,这就是工作原理啦!
当然,sort+cmp的用途远不止于此,更多的用途多在于结构体。
我们知道,sort适用于任何数据类型,包括结构体,比如结构体student
struct student
{
int hight,weight,IQ;
}a[101];
我们则要将学生按身高排序,cmp函数如下:
bool cmp(student a,student b)
{
return a.hight>b.hight;
}
sort函数如下:
sort(a+1,a+n+1,cmp);
这就解决了,是不是很方便?
好了,今天我们的课程到这里就结束了,我们下节课精彩继续。
祝大家元旦快快乐!