算法思想
相邻的元素两两比较,较大的数下沉,较小的数冒起来,这样一趟比较下来,最大(小)值就会排列在一端。整个过程如同气泡冒起,因此被称作冒泡排序。
时间复杂度O( n 2 n^2 n2) ( markdown编辑器 n 2 n^2 n2 用$ n^2 $表示)
适用:冒泡排序适用于数据量很小的排序场景
C/C++
#include
using namespace std;
int a[10]={2,5,3,1,9,6,8,7,0,10};
void sort1()
{
for(int i=0;i<10;i++)
{
for(int j=i+1;j<10;j++)
{
if(a[i]>a[j])//从小到大
{
swap(a[i],a[j]);
}
if(a[i]<a[j])//从大到小
{
swap(a[i],a[j]);
}
}
}
for(int i=0;i<10;i++)
printf("%d ",a[i]);
}
int main()
{
sort1();
return 0;
}
Java
public static void main (String[] args) {
int[] arr=new int []{2,5,3,1,9,6,8,7,0,10};
int l=arr.length;
int t;
for(int i=0;i<l;i++) {
for(int j=i+1;j<l;j++){
if(arr[i]>arr[j])
{
t=arr[i];
arr[i]=arr[j];
arr[j]=t;
}
}
}
for(int i=0;i<l;i++)
System.out.print(arr[i]+" ");
}
算法思想:
先定义第一个元素为最小(大)值,然后再从剩余的未元素中寻找到最小(大)元素,继续放在起始位直到遍历结束
时间复杂度为O( n 2 n^2 n2)
适用: 适用于数据量很小的排序
C/C++
#include
using namespace std;
int a[10]= {2,5,3,1,9,6,8,7,0,10};
void sort1()
{
int minn;
for(int i=0; i<10; i++)
{
minn=i;
for(int j=0; j<10; j++)
{
if(a[minn]>a[j])
{
minn=j;
swap(a[i],a[j]);
}
}
}
for(int i=0; i<10; i++)
{
printf("%d ",a[i]);
}
}
int main()
{
sort1();
return 0;
}
Java
public static void main (String[] args) {
int[] arr=new int []{2,5,3,1,9,6,8,7,0,10};
int l=arr.length;
int t,minn;
for(int i=0;i<l;i++) {
minn=i;
for(int j=0;j<l;j++){
if(arr[minn]>arr[j])
{
minn=j;
t=arr[i];
arr[i]=arr[j];
arr[j]=t;
}
}
}
for(int i=0;i<l;i++)
System.out.print(arr[i]+" ");
}
直接插入排序
算法思想
每一步将一个待排序的数据插入到前面已经排好序的有序序列中,直到插完所有元素为止。
实现步骤
每次从无序部分中取出一个元素,与有序部分中的元素从后向前依次进行比较,并找到合适的位置,将该元素插到有序组当中。
时间复杂度O( n 2 n^2 n2)
适用:待排序的元素个数不多(<=50),且元素基本有序
C/C++
#include
using namespace std;
int a[10]= {2,5,3,1,9,6,8,7,0,10};
void insertsort()
{
int t,j;
//从小到大
for(int i=1;i<10;i++)
{
t=a[i];
for(j=i-1;i>=0&&a[j]>t;j--)
{
a[j+1]=a[j];
}
a[j+1]=t;
}
for(int i=0; i<10; i++)
{
printf("%d ",a[i]);
}
}
int main()
{
insertsort();
return 0;
}
Java
public static void main (String[] args) {
int[] arr=new int []{2,5,3,1,9,6,8,7,0,10};
int l=arr.length;
int t,j;
for(int i=1;i<l;i++) {
t=arr[i];
for(j=i-1;j>=0&&arr[j]>t;j--) {
arr[j+1]=arr[j];
}
arr[j+1]=t;
}
for(int i=0;i<l;i++)
System.out.print(arr[i]+" ");
}
插入排序的优化有 折半插入排序 和 2-路插入排序
折半插入排序
算法思想
二分思想,将待排序的元素与有序部分的元素比较时,不再一 一比较,采取用二分的方式进行比较
C/C++
#include
using namespace std;
int a[10]= {2,5,3,1,9,6,8,7,0,10};
void insertsort()
{
int left,right,mid,t;
for(int i=1;i<10;i++)
{
left=0;
right=i-1;
t=a[i];
//找到合适的插入位置
//如果中间位置元素的值大于要插入的值,则查找区间更改为小的区间
//否则,将区间改为大的区间
while(left<=right)
{
mid=(left+right)/2;
if(a[mid]>t)
right=mid-1;
else
left=mid+1;
}
int j;
for(j=i-1;j>=right+1;j--)
{
a[j+1]=a[j];
}
a[j+1]=t;
}
for(int i=0; i<10; i++)
{
printf("%d ",a[i]);
}
}
int main()
{
insertsort();
return 0;
}
Java
public class Main {
public static void main (String[] args) {
int[] arr=new int []{2,5,3,1,9,6,8,7,0,10};
int l=arr.length;
int left,right,mid,t,j;
for(int i=1;i<l;i++) {
left=0;
right=i-1;
t=arr[i];
while(left<=right){
mid=(left+right)/2;
if(arr[mid]<t)
left=mid+1;
else
right=mid-1;
}
for(j=i-1;j>=right+1;j--){
arr[j+1]=arr[j];
}
arr[j+1]=t;
}
for(int i=0;i<l;i++)
System.out.print(arr[i]+" ");
}
}
快速排序实际上是在冒泡排序基础上的递归分治,快速排序每一轮挑选一个基准元素,并让其它比它大的元素移动到数列一边,比他小的元素移动到另外一边从而把数组拆解为两部分
实现图解
选择一个元素作为基准元素,并将左右两边设置为left指针和right指针,基准元素一般选择第一个元素
判断a[right]与基准元素的大小,如果a[right]大于基准元素,则right指针左移,如果小于基准元素,则a[left]=a[right]
然后再判断a[left]与基准元素的大小,如果a[left]小于基准元素,则left指针右移,如果大于基准元素,则a[right]=a[left]
中间省略一部分while循环图解过程
最终,left指针会与right指针重合,把基准元素的值赋给left指针所指向的位置上
一轮while循环结束后,比基准元素小的都在基准元素的左边,比基准元素大的都在基准元素的右边
#include
using namespace std;
int a[10]={32,11,23,43,24,12,4,31};
void quicksort(int a[],int start,int end)
{
int basic=a[start];//基准元素
//设置left指针和right指针,分别指向数组第一个和最后一个元素
int left=start;
int right=end;
if(start>=end)
return ;
while(left=basic)
{
right--;
}
//right指针指向所在位置的值小于基准元素的值
a[left]=a[right];
//left指针指向所在位置的值小于等于基准元素的值
while(left
希尔排序(又称为缩小增量排序)
算法思想:
设待排序的元素的个数为n,首先取一个整数increment(小于序列总数)作为间隔所有距离为increment的元素放在同一个逻辑数组中,在每一个逻辑数组中分别实行直接插入排序,然后缩小间隔increment,重复上述逻辑数组划分和排序,直到最后increment=1,将所有元素放在同一个数组中排序即可
实现步骤
1、选increment。划分逻辑数组,逻辑数组内进行直接插入排序
2、不断缩小increment,继续在逻辑数组内进行插入排序
3、直到increment=1,在包含所有元素的序列内继续直接插入排序
时间复杂度O( n 2 n^2 n2)
C/C++
#include
using namespace std;
int a[12]= {2,5,3,1,9,6,8,7,0,10};
void shellsort()
{
//初始化increment增量
int increment=10,j,t;
//每次减小increment,直到increment=1
while(increment>1){
//增量的取法之一:除以3向下取整后加1
increment=increment/3+1;
//对每个按increment间距划分的逻辑数组,实行直接插入排序
for(int i=increment;i<10;i++)
{
if(a[i-increment]>a[i])
{
t=a[i];
j=i-increment;
//移动元素并寻找位置
while(j>=0&&a[j]>t){
a[j+increment]=a[j];
j-=increment;
}
//插入元素
a[j+increment]=t;
}
}
}
for(int i=0; i<10; i++)
{
printf("%d ",a[i]);
}
}
int main()
{
shellsort();
return 0;
}
堆排序是指利用堆积树这种数据结构所设计的一种排序算法,是选择排序的一种
堆是具有下列性质的完全二叉树
每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆
每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆
算法思想
将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根结点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次小值。如此反复执行,就能得到一个有序序列
需要用到的树的性质
假设父节点下标为k(k>0),那么其左孩子下标为2 * k,右孩子下标为2 * k+1
实现堆排序需要解决2个问题
如何将一个无序序列构建成一个堆?
如何在输出堆顶元素后,调整剩余元素成一个新的堆?
时间复杂度也为O(nlogn)
#include
using namespace std;
int a[12]={0,3,2,5,6,1,4,7,9,0,8};
void heapadjust(int a[],int s,int m)//一次筛选
{
int t=a[s];
for(int j=2*s;j<=m;j=j*2)//沿较大的孩子节点向下筛选
{
if(j<m&&a[j]<a[j+1])
j++; //j记录较大的下标
if(t>a[j])
break;
a[s]=a[j];
s=j;
}
a[s]=t;
}
void heapsort(int a[],int n)
{
int t;
for(int i=n/2;i>0;i--)//通过循环初始化顶堆
{
heapadjust(a,i,n);
}
for(int i=n;i>0;i--)
{
swap(a[1],a[i]);//将顶堆记录与未排序的最后一个记录交换
heapadjust(a,1,i-1);//重新调整为顶堆
}
}
int main()
{
int n=10;
heapsort(a,n);
for(int i=0;i<=10;i++)
printf("%d ",a[i]);
return 0;
}
算法思想
假设待排序的元素输入符合某种均匀分布,例如数据均匀分布在[ 0,1)区间上,则可将此区间划分为10个小区间,称为桶,对散布到同一个桶中的元素再排序。
实现步骤
1、先将待排序元素分配n个区间
2、对应数放在对应区间的桶内
3、将桶内的数排序
4、按顺序输出
时间复杂度
平均时间复杂度:O(n + k)
最佳时间复杂度:O(n + k)
最差时间复杂度:O(n ^ 2)
空间复杂度:O(n * k)
注:n是待排序元素的个数、k是分桶的数量
桶排序的时间复杂度,取决与对桶之间数据进行排序的时间复杂度,因为其它部分的时间复杂度都为O(n)。所以,桶划分越小,桶之间的数据越少,排序所用的时间就越少。但相应的空间消耗就会增大。桶排序最好情况下使用线性时间O(n)
#include
#include
using namespace std;
int a[12]={3,1,29,31,20,10,45,67,33,42};
void Tongsort(int a[],int n)
{
int minn=a[0];
int maxx=minn;
for(int i=1;i<n;i++)
{
if(a[i]<minn)
minn=a[i];
if(a[i]>maxx)
maxx=a[i];
}
//区间间隔
int interzone=(maxx-minn)/n+1;
//printf("%d---\n",interzone);
vector<vector<int>>v;
for(int i=0;i<interzone;i++)
{
vector<int>v1;
v.push_back(v1);
}
for(int j=0;j<n;j++)
{
int num=(a[j]-minn)/n;
v[num].push_back(a[j]);
}
for(int i=0;i<interzone;i++)
std:sort(v[i].begin(),v[i].end());
int index=0;
for(int i=0;i<interzone;i++)
{
for(int j=0;j<v[i].size();j++)
{
a[index++]=v[i][j];
}
}
}
int main()
{
Tongsort(a,10);
for(int i=0;i<10;i++)
cout<<a[i]<<" ";
return 0;
}
归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法,归并排序对序列的元素进行逐层折半分组,然后从最小分组开始比较排序,合并成一个大的分组,逐层进行,最终所有的元素都是有序的
时间复杂度是O(nlogn)
思路图解
要排序这样一个数组的时候,归并排序法首先将这个数组分成一半
排序左边与右边数组的元素并归并,当我们对左边的数组和右边的数组排序时,再分别将左边的数组和右边的数组分成一半,然后对每一个部分先排序,再归并
最终,每一个部分只有一个元素,此时便不用排序,归并即可
归并到上一部分后再次归并,直到最后排序完成
/*
int a[10]= {10,23,35,4,10,30,24,56};
10,23,35,4 || 10,30,24,56
10,23 || 35,4 || 10,30 || 24,56
10 23 || 4 35 || 10 30|| 24 56
4 10 23 35 || 10 24 30 56
4 10 10 23 24 30 35 56
*/
#include
using namespace std;
int a[10]= {10,23,35,4,10,30,24,56};
void merge(int a[],int l,int r,int mid)
{
int b[r-l+1];//r-l+1为数组大小,也可以不计算自行开空间
int i,j,index=0;
for(int k=l;k<=r;k++)
b[index++]=a[k];
i=l;
j=mid+1;
for(int k=l;k<=r;k++)
{
if(i>mid)//遍历到右边数组,右边已经是排序好的,直接存储即可
{
a[k]=b[j-l];
j++;
}
else if(j>r)//右边数组遍历结束,存储左边数组的数
{
a[k]=b[i-l];
i++;
}
else if(b[i-l]>b[j-l])//左边的数大于右边的数,存储右边较小的数
{
a[k]=b[j-l];
j++;
}
else //左边的数小于右边的数,存储左边较小的数
{
a[k]=b[i-l];
i++;
}
//printf("%d--\n",a[k]);
}
}
void merge_sort(int a[],int l,int r)
{
if(l>=r)
return ;
int mid=(l+r)/2;
merge_sort(a,l,mid);
merge_sort(a,mid+1,r);
merge(a,l,r,mid);
}
void mergesort(int a[],int l,int r)
{
merge_sort(a,l,r-1);
}
int main()
{
mergesort(a,0,8);
for(int i=0;i<8;i++)
cout<<a[i]<<" ";
return 0;
}
算法思想
遍历待排序的元素,将每个元素对应的计数数组的下标的元素+1,按顺序输出计数数组下标的值,元素的值是多少,就输出几次
时间复杂度是O(N+M)(N是待排序数组的大小,M是计数数组的大小)
#include
using namespace std;
int a[10]= {10,23,35,4,10,30,24,56};
int main()
{
int maxx=0;
for(int i=0; i<8; i++)
maxx=max(maxx,a[i]);
int num[maxx+5];
memset(num,0,sizeof(num));
for(int i=0; i<8; i++)
num[a[i]]++;
for(int i=0; i<=maxx; i++)
{
if(num[i])
{
for(int j=0; j<num[i]; j++)
{
cout<<i<<" ";
}
}
}
return 0;
}
优化版
如果遇到数组里的数很大的情况,按照上面的解法计数数组需要开很大
解释:{146,143,150,144,147,153,157,156}这种情况,最大值是157,按照上面的解法计数数组的大小要至少要开158,如此看来,计数数组下标0-142都是没有用到的,这样就增加空间复杂度
那么如何降低空间复杂度?
我们可以创建一个长度为最大值-最小值+1(157-143+1)的计数数组,计数数组的偏移量为序列的最小值143
#include
using namespace std;
int a[10]= {146,143,150,144,147,153,157,156};
int main()
{
int maxx=0,minn=0x3f3f3f;
for(int i=0; i<8; i++)
{
maxx=max(maxx,a[i]);
minn=min(minn,a[i]);
}
int N=maxx-minn+1;
int num[N+1];
memset(num,0,sizeof(num));
for(int i=0;i<8;i++)
num[a[i]-minn]++;
for(int i=minn;i<=maxx;i++)
{
for(int j=0;j<num[i-minn];j++)
{
cout<<i<<" ";
}
}
return 0;
}