1.冒泡排序
冒泡排序,相邻的数据进行两两比较,小数放在前面,大数放在后面,这样一趟下来,最小的数就被排在了第一位,第二趟也是如此,如此类推,直到所有的数据排序完成。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,我们不会无聊地把他们俩交换;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。【冒泡排序可谓是最经典的排序算法了,它是基于比较的排序算法,时间复杂度为O(n^2),其优点是实现简单,n较小时性能较好。
#include<iostream>
using namespace std;
void bubble_sort(int*a,int length)
{
int temp=0;
int flag=0;
for(int i=0;i<length-1;i++)//进行n-1趟,不是n趟
{
flag=0;
for(int j=length-1;j>=i;j--)//j>=i是前面i个已经排好序了,每一趟都把最小的冒上去
{
if(a[j-1]>a[j])
{
temp=a[j];
a[j]=a[j-1];
a[j-1]=temp;
flag=1;
}
}
if(flag==0)
return;//若一趟下来一个都没有交换,证明已经排好序了,提前结束
}
}
int main()
{
int a[5]={2,1,3,6,5};
bubble_sort(a,5);
for(int i=0;i<5;i++)
cout<<a[i]<<" ";
}
2.插入排序
插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。
#include<iostream>
using namespace std;
void insert_sort(int*a,int length)
{
int temp,i,j;
for( i=1;i<length;i++)
{
temp=a[i];
for(j=i-1;j>=0&&temp<a[j];j--)
{
a[j+1]=a[j];//前面的比我大,那前面的就要往后挪一位
}
a[j+1]=temp;//找到我的位置了,放进去
}
}
int main()
{
int a[5]={2,1,3,6,5};
insert_sort(a,5);
for(int i=0;i<5;i++)
cout<<a[i]<<" ";
}
3.选择排序
选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n-1个元素,第n个元素不用选择了,因为只剩下它一个最大的元素了。O(n^2),不稳定。
#include<iostream>
using namespace std;
void select_sort(int*a,int length)
{
int min,temp;
for(int i=0;i<length;i++){//找到最小的元素,将其放到a【i】中
min=i;
for(int j=i;j<length;j++){
if(a[j]<a[min])
min=j;
}
temp=a[min];
a[min]=a[i];
a[i]=temp;
}
}
int main()
{
int a[5]={2,1,3,6,5};
select_sort(a,5);
for(int i=0;i<5;i++)
cout<<a[i]<<" ";
}
4.快速排序
快速排序是目前在实践中非常高效的一种排序算法,它不是稳定的排序算法,平均时间复杂度为O(nlogn),最差情况下复杂度为O(n^2)。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
#include<iostream>
using namespace std;
int quicksort(int v[],int left,int right)
{
if(left<right)
{
int key=v[left];//选取第一个元素作为划分基准
int low =left;
int high=right;
while(low<high)//终止条件为头尾两个指针相遇
{
while(low<high&&v[high]>key)
{
high--;
}
v[low]=v[high];//从右往左扫,若比key值小,则交换,交换完后,又从左往右扫
while(low<high&&v[low]<key)
{
low++;
}
v[high]=v[low];//从左往右扫,若比key值大,则交换,交换完后,又从右往左扫
}
v[low]=key;//不要忘了划分的基准值
quicksort(v,left,low-1);
quicksort(v,low+1,right);
}
}
int main()
{
int a[10]={2,4,100,24,12,29,7,23,45,67};
quicksort(a,0,9);
for(int i=0;i<10;i++)
{
cout<<a[i]<<'\t';
}
}
5.堆排序
讲解:https://jingyan.baidu.com/article/5225f26b057d5de6fa0908f3.html
#include <iostream>
using namespace std;
void siftdown(int a[],int i,int len){
int temp=a[i];//a[i]是要下移的元素
int largest;
for(largest=2*i+1;largest<len;largest=largest*2+1){//默认最大的为左子节点,同时要迭代完整棵子树
if(a[largest]<a[largest+1]&&largest+1<len){//右子节点是最大的情况
largest++;
}
if(a[largest]>temp){//将两个子节点中最大的换给父节点
a[i]=a[largest];
i=largest;//必须记录最大值的下标,因为下面还可能继续迭代
}
}
a[i]=temp;
}
void makeheap(int a[],int len){//建堆是倒着来的,从最后一个节点开始siftdown,
//由于叶节点可以直接跳过,因此从最后一个非叶节点开始,也就是i=len/2-1
for(int i=len/2-1;i>=0;i--)
siftdown(a,i,len);
}
int main()
{
int a[10]={2,45,20,3,9,6,5,109,200,10};
makeheap(a,10);
for(int j=9;j>=1;j--){
int temp=a[0];
a[0]=a[j];
a[j]=temp;
siftdown(a,0,j);//建完堆之后,堆顶a[0]为最大值,把它和最后一个元素a[j]交换,
//这样的话,就需要对a[0]进行siftdown,同时由于最后一个元素已经到了争取的位置,因此不再牵涉它
}
for(int i=0;i<10;i++){
cout<<a[i]<<" ";
}
}
5.基数排序
#include<iostream>
#include<vector>
using namespace std;
int getmax(int a[],int size){//获取最大有多少位
int maxnum=a[0];
int max=-1;//个位称为“0位”
for(int i=0;i<size;i++){//先求最大的数进而求得最大位数
if(a[i]>maxnum)
maxnum=a[i];
}
while(maxnum){
maxnum=maxnum/10;
max++;
}
return max;
}
void radixsort(int a[],int size,int max){
vector<int> vec[10];//这是一个数组,数组元素的类型是int向量
int counts[10];//counts[i]的值表示这个位置为i的数的个数,比如个位为2的数字的个数
int radix=1;
for(int i=0;i<max;i++){//每一位都要做一次
for(int p=0;p<10;p++)
counts[p]=0;
for(int p=0;p<10;p++)
vec[p].clear();//每次遍历要清空
for(int j=0;j<size;j++){//例如个位为2的数则添加到vec[2]中
int k=a[j]/radix%10;//获得该位的数字
vec[k].push_back(a[j]);
counts[k]++;
}
int k=0;
for(int i=0;i<10;i++){
for(int j=0;j<counts[i];j++){
a[k]=vec[i][j];
k++;
}
}
radix=radix*10;
}
}
int main(){
int a[5]={123,45,3476,91,32};
int max=getmax(a,5);
radixsort(a,5,max);
for(int i=0;i<5;i++){
cout<<a[i]<<" ";
}
}
6.希尔排序
将无序数组分割为若干个子序列,子序列不是逐段分割的,而是相隔特定的增量的子序列,对各个子序列进行插入排序;然后再选择一个更小的增量,再将数组分割为多个子序列进行排序……最后选择增量为1,即使用直接插入排序,使最终数组成为有序。
#include <iostream>
using namespace std;
void shell_sort(int*a,int length)
{
int i,j,gap;
for(gap=length/2;gap>0;gap/=2)
{
for(i=0;i<gap;i++)
for(j=i+gap;j<length;j+=gap)
{
if(a[j]<a[j-gap])
{
int temp=a[j];
int k=j-gap;
while(k>=0&&a[k]>temp)
{
a[k+gap]=a[k];
k-=gap;
}
a[k+gap]=temp;
}
}
}
}
int main()
{
int a[5]={2,1,3,6,5};
shell_sort(a,5);
for(int i=0;i<5;i++)
cout<<a[i]<<" ";
}
7.归并排序
归并排序具体工作原理如下(假设序列共有n个元素):
将序列每相邻两个数字进行归并操作(merge),形成floor(n/2)个序列,排序后每个序列包含两个元素
将上述序列再次归并,形成floor(n/4)个序列,每个序列包含四个元素
重复步骤2,直到所有元素排序完毕
归并排序是稳定的排序算法,其时间复杂度为O(nlogn),如果是使用链表的实现的话,空间复杂度可以达到O(1),但如果是使用数组来存储数据的话,在归并的过程中,需要临时空间来存储归并好的数据,所以空间复杂度为O(n)
#include <iostream>
using namespace std;
//对一个数组的两部分进行合并排序
void Merge(int a[],int begin,int last,int mid,int temp[])//注意这个参数设计
{
int i=begin;
int j=mid+1;
int k=0;
while(i<=mid&&j<=last)
{
if(a[i]<=a[j])
temp[k++]=a[i++];
else
temp[k++]=a[j++];
}
while(i<=mid)
temp[k++]=a[i++];
while(j<=last)
temp[k++]=a[j++];
//将排好序的写回去
for(int i=0;i<k;i++)
a[begin+i]=temp[i];
}
//递归的分割数组
void Divide(int a[],int begin,int last,int temp[])
{
if(last>begin)
{
int mid=(begin+last)/2;
Divide(a,begin,mid,temp);
Divide(a,mid+1,last,temp);
Merge(a,begin,last,mid,temp);
}
}
int main()
{
int a[6]={1,3,5,2,4,6};
int*temp=new int[6];
Divide(a,0,5,temp);
for(int i=0;i<6;i++)
cout<<a[i]<<"\t";
cout<<endl;
return 0;
}
总结
时间复杂度:快速排序最好是O(n*logn),堆排序和归并排序是O(n*logn),其余都是O(n^2)
稳定性:不稳定的有,快速排序,选择排序,堆排序,希尔排序。其余稳定
如何选择:当数据规模较小时,应该选择直接插入排序或冒泡排序。任何排序算法在数据量小时基本体现不出差距。快速排序对于已经基本有序的数据会浪费大量不必要的步骤。