冒泡排序
较小的数字会如同气泡一样慢慢的漂浮到上面,因此被称为冒泡排序
话不多说,见代码:
/*交换两个数字*/
void mySwap(int *ori,int *des)
{
int temp=*ori;
*ori=*des;
*des=temp;
}
/*冒泡排序*/
void bubbleSort(vector<int> &arr,int bgn,int end)
{
bool isloop=true;
for(int i=end;true==isloop&&i>bgn;i--)
{
isloop=false;
for(int j=bgn+1;j<i;j++)
{
if(arr[j]<arr[j-1])
{
mySwap(&arr[j],&arr[j-1]);
isloop=true;
}
}
}
}
int main()
{
int a[]={10,3,5,2,98,34,52};
//sizeof实际上是获取了数据在内存中所占用的存储空间,以字节为单位来计数。
int length=sizeof(a)/sizeof(a[0]);
vector<int>numbers(a,a+length);
bubbleSort(numbers,0,length);
for(int j=0;j<length;j++)
{
printf("%d ",numbers[j]);
}
system("pause");
}
时间复杂度为O(n^2)
选择排序
选择排序就是通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i个记录交换
/*简单选择排序*/
void SelectSort(int *arr,int size)
{
int i,j,min;
for(i=0;i<size-1;i++)
{
min=i; //将当前下标定义为最小值下标
for(j=i+1;j<size;j++)
{
if(arr[min]>arr[j]) //如果有小于当前最小值的关键字
{
min=j; //将此关键字的下标赋给min
}
}
if(min!=i) //如果min不等于i,说明找到最小值,交换
{
mySwap(&arr[i],&arr[min]);
}
}
}
int main()
{
int a[]={10,3,5,2,98,34,52,45,32,36,100};
//sizeof实际上是获取了数据在内存中所占用的存储空间,以字节为单位来计数。
int length=sizeof(a)/sizeof(a[0]);
SelectSort(a,length);
for(int j=0;j<length;j++)
{
printf("%d ",a[j]);
}
system("pause");
}
时间复杂度为O(n^2)特点就是交换移动数据特别少,性能上优于冒泡排序
插入排序
插入排序的基本操作是将一个记录插入到已经拍好序的有序表中,从而得到一个新的,记录数增1的有序表
/*直接插入排序*/
void InsertSort(int *arr,int size)
{
//新插入一个数据的时候,排序过后的数组都是
//从小到大排列好的,所以我们需要从后往前查找,比新插入数据大的数我们向后移一位
//直到找到比我们要插入的数字还小的值时退出循环,然后把待插入值放到j+1这个位置
//这个时候我们需要一个变量j作为标识
for(int i=1;i<size;i++)
{
int temp=arr[i]; //从待插入组取出第一个元素。
int j;
for(j=i-1;j>=0;j--) //i-1即为有序组最后一个元素(与待插入元素相邻)的下标
{
if(arr[j]>temp) //如果当前有序表的下标大于待插入数组的最后一个元素
{
arr[j+1]=arr[j]; //将当前有序表元素向后移一位
}
else
{
break; //直到找到比我们要插入的数字还小的值时退出循环
}
}
arr[j+1]=temp; //把待插入值放到j+1这个位置
}
}
时间复杂度为O(N^2) 同样的时间复杂度上,性能上要优于冒泡排序和选择排序。
希尔排序
/*希尔排序*/
void InsertI(int *arr,int gap,int i)
{
/*将arr[i]插入到所在分组的正确位置上
arr[i]所在分组为:
arr[i-2*gap],arr[i-gap],arr[i],arr[i+gap],arr[i+2*gap]*/
int insert=arr[i];
int j;
for(j=i-gap;j>=0&&insert<arr[j];j-=gap)
{
arr[j+gap]=arr[j];
}
arr[j+gap]=insert;
}
void ShellSort(int *arr,int size)
{
//进行分组,最开始时的增量(gap)为数组长度的一半
for(int gap=size/2;gap>0;gap=gap/2) //设置增量,每次都是gap/2
{
//对各个分组进行插入排序
for(int i=gap;i<size;i++)
{
//将arr[i]插入到所在分组的正确位置上
InsertI(arr,gap,i);
}
}
}
希尔排序通过将全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)。
ShellSort的关键点在于gap序列的设计。
希尔排序的时间复杂度为O(N^3/2) 要好于上诉的
O(N^2) 另外由于记录是跳跃式的移动,希尔排序并不是一种稳定的排序算法,希尔排序的发明,使得我们突破了慢速排序的时代(超越了时间复杂度O(N^2)),之后,相应的更为高效的排序算法也相继出现了。
堆排序
/*堆排序*/
void HeapAdjust(int *arr,int s,int m)
{
int temp,j;
temp=arr[s];
for(j=2*s+1;j<=m;j=2*j+1) //当数组0不为哨兵时,非终端节点i的左节点为2i+1 右节点为2(i+1)
{
if(j<m&&arr[j]<arr[j+1])
++j; //记录非终端节点的左右孩子中较大数的坐标
if(temp>=arr[j]) //如果非终端节点大于左右孩子的值,则不做任何变化
break;
arr[s]=arr[j]; //如果如果非终端节点小于左右孩子的值,则交换值
s=j;
}
arr[s]=temp;
}
void HeapSort(int *arr,int size)
{
int i;
for(i=size/2-1;i>=0;i--) //将顺序表数组构建成一个大顶堆
{
HeapAdjust(arr,i,size-1); //下标0 1,2,3 均为有孩子节点,故构建他们
}
for(i=size-1;i>0;i--)
{
mySwap(&arr[0],&arr[i]); //将堆顶记录和当前未经排序子序列的最后一个记录交换
HeapAdjust(arr,0,i-1); //维护从下标为i-1到0的子数组
}
}
堆排序的时间复杂度为O(nlogn) 由于堆排序对原始记录的排序状态并不敏感,因此无论是最好,最坏和平均时间复杂度均为O(nlogn)。这在性能上显然要远远好于冒泡,简单选择,直接插入排序的性能。
由于堆排序初始构建堆所需的比较次数较多,因此,它并不适合待排序序列的个数较少的情况。
归并排序
首先来看归并排序要解决的第一个问题:两个有序的数组怎样合成一个新的有序数组:
比如数组1{ 3,5,7,8 }数组2为{ 1,4,9,10 }:
首先那肯定是创建一个长度为8的新数组咯,然后就是分别从左到右比较两个数组中哪一个值比较小,然后复制进新的数组中:比如我们这个例子:
{ 3,5,7,8 } { 1,4,9,10 } { }一开始新数组是空的。
然后两个指针分别指向第一个元素,进行比较,显然,1比3小,所以把1复制进新数组中:
{ 3,5,7,8 } { 1,4,9,10 } { 1, }
第二个数组的指针后移,再进行比较,这次是3比较小:
{ 3,5,7,8 } { 1,4,9,10 } { 1,3, }
同理,我们一直比较到两个数组中有某一个先到末尾为止,在我们的例子中,第一个数组先用完。{ 3,5,7,8 } { 1,4,9,10 } { 1,3,4,5,7,8 }
最后把第二个数组中的元素复制进新数组即可。
{ 1,3,4,5,7,8,9,10 }
这其实就是归并排序的最主要想法和实现,归并排序的做法是:
将一个数组一直对半分,问题的规模就减小了,再重复进行这个过程,直到元素的个数为一个时,一个元素就相当于是排好顺序的。
接下来就是合并的过程了,合并的过程如同前面的描述。一开始合成两个元素,然后合并4个,8个这样进行。
所以可以看到,归并排序是“分治”算法的一个经典运用。
/*归并排序*/
void Merge(int *a,int left,int mid,int right)
{
int n1=mid-left+1; //左部分的元素个数
int n2=right-mid; //右半部分元素的个数
int *L=new int[n1+1];
int *R=new int[n2+1];
for(int i=0;i<n1;i++)
{
L[i]=a[left+i];
}
for(int j=0;j<n2;j++)
{
R[j]=a[mid+j+1];
}
L[n1]=11111111;
R[n2]=11111111;
数组L从0~n1-1存放,第n1个存放int型所能表示的最大数,即认为正无穷,这是为了
//处理合并时,比如当数组L中的n1个元素已经全部按顺序存进数组a中,只剩下数组R的
//部分元素,这时因为R中剩下的元素全部小于11111111,则执行else语句,直接将剩下的
//元素拷贝进a中
for(int i=0,j=0,k=left;k<=right;k++)
{
if(L[i]<=R[j])
{
a[k]=L[i++];
}
else
{
a[k]=R[j++];
}
}
delete []L;
delete []R;
}
void MergeSort(int *arr,int left,int right)
{
if(left<right)
{
int m=(left+right)/2;
MergeSort(arr,left,m);
MergeSort(arr,m+1,right);
Merge(arr,left,m,right);
}
}
int main()
{
int a[]={10,3,5,2,98,34,52,45,32,36,100};
//sizeof实际上是获取了数据在内存中所占用的存储空间,以字节为单位来计数。
int length=sizeof(a)/sizeof(a[0]);
MergeSort(a,0,length-1);
//ShellSort(a,length);
//QuickSort(a,0,length-1);
for(int j=0;j<length;j++)
{
printf("%d ",a[j]);
}
system("pause");
}
归并排序的最好,最坏,平均情况下的时间复杂度均为O(nlogn),由于归并排序在归并过程中需要与原始记录序列同样数量的存储空间存放归并结果,以及递归时深度为O(log2n)的栈空间,因此空间复杂度为O(n+logn)
快速排序
快速排序算法是冒泡排序的一种改进,快速排序也是通过逐渐消除待排序的无序序列中逆序元素来实现排序的
快速排序之所比较快,因为相比冒泡排序,每次交换是跳跃式的。每次排序的时候设置一个基准点,将小于等于基准点的数全部放到基准点的左边,将大于等于基准点的数全部放到基准点的右边。这样在每次交换的时候就不会像冒泡排序一样每次只能在相邻的数之间进行交换,交换的距离就大的多了。因此总的比较和交换次数就少了,速度自然就提高了。当然在最坏的情况下,仍可能是相邻的两个数进行了交换。因此快速排序的最差时间复杂度和冒泡排序是一样的都是O(N2),它的平均时间复杂度为O(NlogN)。
/*快速排序*/
int Partition(int *arr,int left,int right) //找基准数,划分
{
int pivotkey;
pivotkey=arr[left];
while (left<right)
{
while (left<right&&arr[right]>=pivotkey)
{
right--;
}
mySwap(&arr[left],&arr[right]);
while (left<right&&arr[left]<=pivotkey)
{
left++;
}
mySwap(&arr[left],&arr[right]);
}
return left;
}
void QuickSort(int *arr,int left,int right)
{
if(left>right)
return;
//将数组进行划分(partition),将比基准数大的元素都移至枢轴右边,将小于等于基准数的元素都移至枢轴左边。
// 并返回基准数的位置
int j=Partition(arr,left,right);
QuickSort(arr,left,j-1);
QuickSort(arr,j+1,right);
}
int main()
{
int a[]={10,3,5,2,98,34,52,45,32,36,100};
//sizeof实际上是获取了数据在内存中所占用的存储空间,以字节为单位来计数。
int length=sizeof(a)/sizeof(a[0]);
//MergeSort(a,0,length-1);
//ShellSort(a,length);
QuickSort(a,0,length-1);
for(int j=0;j<length;j++)
{
printf("%d ",a[j]);
}
system("pause");
}
在最优的情况下,快速排序的算法的时间复杂度为O(nlogn),在最坏的情况下,其时间复杂度为O(N2)平均情况下为O(nlogn)
空间复杂度 最优情况下:O(nlogn) 平均情况下:O(nlogn) 最坏情况下:O(n)
排序方法 | 平均情况 | 最好情况 | 最坏情况 | 辅助空间 | 稳定性 |
---|---|---|---|---|---|
冒泡排序 | O(N2) | O(N) | O(N2) | O(1) | 稳定 |
简单选择排序 | O(N2) | O(N2) | O(N2) | O(1) | 稳定 |
直接插入排序 | O(N2) | O(N) | O(N2) | O(1) | 稳定 |
希尔排序 | O(NlogN)-O(N2) | O(N1.3) | O(N2) | O(1) | 不稳定 |
堆排序 | O(NlogN) | O(NlogN) | O(NlogN) | O(1) | 不稳定 |
归并排序 | O(NlogN) | O(NlogN) | O(NlogN) | O(N) | 稳定 |
快速排序 | O(NlogN) | O(NlogN) | O(N2) | O(logN)-O(N) | 不稳定 |