目录
题目:
零。 全文概述
本文将介绍三种排序方法(冒泡,快排,归并),其中主要介绍快速排序
前置知识——swap()函数:
形式:swap(a,b)
作用:会直接交换a,b的值
壹。 冒泡排序
原理:
冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两元素,如果他们的顺序错误就把他们交换过来,走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢”浮”到数列的顶端。
过程:
以从小到大排序为例,第一轮比较后,所有数中最大的那个数就会浮到最右边;第二轮比较后,所有数中第二大的那个数就会浮到倒数第二个位置(即每次循环找到其中的最大值放到最右边)……就这样一轮一轮地比较,最后实现从小到大排序。
时间复杂度:
双重循环,n的平方,很低效
代码:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 110;
void Bubble_sort(int a[], int size)
{
for ( int i = 0; i < size-1;i ++)//size-1是因为不用与自己比较,所以比的数就少一个
{
int count = 0; //每次循环前都初始化一下
for ( int j = 0; j < size-1 - i; j++) //size-1-i是因为每一趟就会少一个数比较
{
if (a[j] > a[j+1])//升序,前一个数和后一个数比较,前数大则与后一个数换位置
{
swap(a[j],a[j+1]);
count = 1;
}
}
if (count == 0) //如果某一趟没有交换位置,则说明已经排好序,直接退出循环
break;
}
}
int main()
{
int a[N];
cout<<"请输入10个数"<<endl;
for ( int i = 0 ; i < 10 ; i++) cin>>a[i];
Bubble_sort(a, 10);
cout<<"排序后的数组"<<endl;
for ( int i = 0 ; i < 10 ; i++) cout<<a[i]<<" ";
return 0;
}
唯一值得注意的是其中count的设置——相当于剪枝:可以提前知道已经排好了序,提前推出循环
贰。 快速排序
题目:
步骤(原理):
我们不管代码如何实现,先知道步骤和去理解为什么经过这样的三步之后就会排好序了(此时以升序排序为例)
第一步:确定分界点
去找到一个分界点和这个分界点对应的值x,随便找一个点就可以,这里假设我们找的是中点。现在x的值就是我们分界点处的值
第二步:处理区间
调整区间——此时要达成的效果如图,保证分界点左侧的值都是小于等于x,分界点右侧的值都是大于等于x的
第三步:分别递归处理分界点两侧的两坨数组
经过这样的三步,我们其实就完成了排序过程。让我们来尝试理解一下。
在第二步进行后,我们此时保证了分界点左边的值一定是小于等于右边的(即左边的最大值一定小于等于右边的最小值)。换言之,如果把分界点左边的数组看作一坨东西,右边的数组也看作一坨东西,那么现在这两坨东西是有序的了。
而在第三步,我们相当于分别递归处理了左右两边的两坨数组,使两坨数组内部分别是有序的了
因此,综合第二步和第三步来看,此时两坨是有序的,两坨内部又分别是有序的;所以自然整个数组都是有序的了
实现第二步 处理区间:
三步中,第一步和第三步都是比较简单的;所以主要说明一下如何实现第二步区间调整
法1(只是介绍,并不使用这个方法去求):空间换时间
法2(用此方法):双指针法
如此通过两个双指针分别指向左右两端,并进行一次遍历即可完成区间调整
双指针法的易错点在于:边界问题——解决办法是背模板,记住其中i和j的取值
相关代码:
//第二步:处理区间
// 双指针分别指向最左与最右
// 此时由于后续操作中都是先移动一步指针(do-while),因此此时我们应该把两侧指针都提前移动一步到达边界的左右两侧
int i=l-1,j=r+1;
// 双指针的处理
while(i<j) //结束的条件是两个指针相遇
{
do i++;while (q[i]<x);
do j--;while (q[j]>x);
// 交换前去优先判断一下两侧的指针能否保证左边的大于右边的
if(i<j) swap(q[i],q[j]);
}
此时将i和j都分别左移右移一位,且使用do-while就是为了避免边界问题的出现
#:为了简洁,上述代码省略了中括号,完整版如下
while(i<j)
{
do
{
i++;
}while (q[i]<x);
do
{
j--;
} while (q[j]>x);
if(i<j){
swap(q[i],q[j]);
}
}
注意do-while使用时while的最后面也要加上分号
代码实现:
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
int n;
const int N=1e7;
// 开为全局变量可以不用再传入函数那一步了,同时也会默认全部取0
int q[N];
void quicksort(int q[],int l,int r)
{
//由于是递归,所以需要一个边界条件
// 边界条件,写成l==r也可以的。
if(l>=r) return ;
//第一步:确定分界点
// 找的一个分界点(随便取,此时取的是中点)
int x=q[l+r>>1]; //位运算
//第二步:处理区间
// 双指针分别指向最左与最右
// 此时由于后续操作中都是先移动一步指针(do-while),因此此时我们应该把两侧指针都提前移动一步到达边界的左右两侧
int i=l-1,j=r+1;
// 双指针的处理
while(i<j) //结束的条件是两个指针相遇
{
do i++;while (q[i]<x);
do j--;while (q[j]>x);
// 交换前去优先判断一下两侧的指针能否保证左边的大于右边的
if(i<j) swap(q[i],q[j]);
}
//第三步:递归处理
// 对左右两边分别递归
quicksort(q,l,i);
quicksort(q,j+1,r);
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&q[i]);
}
quicksort(q,0,n-1);
for(int i=0;i<n;i++){
printf("%d ",q[i]);
}
}
quicksort函数内部就是分别实现步骤中所说的三步即可,要注意不要忘记了最前面要加上一个递归函数递归结束的条件
叁。 归并排序
题目:
步骤:
与快排的对比
稳定性:
时间复杂度:
代码实现:
#include <cstdio>
#include <iostream>
using namespace std;
const int N=1e5+10;
int n;
int q[N],tmp[N];
// q数组是存储的数组,而在归并排序的每一次的归并中,我们都要去把两组数归并到一个数组tmp临时数组中
// 临时数组只是用于中间的存储,最后还是要归于到原数组q中去
void merge_sort(int q[] , int l , int r)
{
if(l>=r){
return ;
}
// 递归边界条件:当区间长度为1时返回
int mid =l+r>>1;
// 归并的分界点选为中间处(此时用位运算表示/2,更高效)
merge_sort(q,l,mid);
merge_sort(q,mid+1,r);
// 归并是先递归再排列:因从我们要先去递归到区间长度为1时再依次往回找(dfs)
int k=0;
// tmp临时数组:从0开始依次归并填充临时数组
int i=l,j=mid+1;
// 双指针去分别指向了两个数组的开端
while(i<=mid && j<=r){
if(q[i]<=q[j]){
tmp[k++]=q[i++];
}
else{
tmp[k++]=q[j++];
}
}
// 去依次比较指针所指数的大小,并归并到tmp中
// 比较直到其中的一个区间完全比较完了,进行下面的操作(直接把剩下的那个区间全部接到临时数组里去)
// 边界问题:上面的操作中每次都包含了i++(j++),因此最后的时候走完区间的i与j是多了1的,所以这里的判断条件是<=而非=
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再返还到q里面去
return;
}
int main()
{
cin>>n;
for(int i=0;i<n;i++){
scanf("%d",&q[i]);
}
merge_sort(q,0,n-1);
for(int i=0;i<n;i++){
printf("%d ",q[i]);
}
return 0;
}