排序的定义:
重新排列表中的元素,使表中元素满足按关键字递增或递减的过程。
算法稳定性
如果待排序表中有两个元素Ri和Rj,关键字Ri=Rj,排序前Ri在Rj前面,某一排序算法后,Ri还在Rj前面,这个排序算法就是稳定的,否则称不稳定的。
排序过程中,根据数据元素是否完全在内存中,排序算法分为两类:内部排序和外部排序。
一、 插入排序
插入排序是一种简单直观的排序方法,其基本思想是每次将一个待排序的记录按关键字大小插入到前面已经拍好的子序列中,直到全部记录插入完成。
引申出三个重要的排序算法:直接插入排序,折半插入排序,希尔排序
直接插入是最简答最直观的,插入只需三步
L[1…i-1] L(i) 无序序列
(1)查找出L(i)在有序序列中的插入位置k
(2)L[k…i-1]所有元素全部后移一个位置
(3)L(i)复制到L(k)
直接插入排序的时间复杂度是O(n^2) 是稳定的
折半插入排序
改进了直接插入的第一步,采用折半法查找位置
时间复杂度任然是O(n^2) 是稳定的
希尔排序
基本思想是:先将待排序表分割若干特殊子表,分别进行插入排序,整个表中基本有序时,对全体记录进行一次直接插入排序。取一个小于n的步长d1,全部记录分成d1组,然后取d2<d1直到d=1。
时间复杂度为O(n^2) 是不稳定的
二、交换排序
所谓交换,是根据序列中的两个关键字的比较结果对换记录所在位置。
冒泡排序
基本思想是:假设待排序表长为n,从后往前两两比较相邻元素的值,逆序则交换,一趟冒泡可以锁定一个元素的位置,待排序列减少一个元素,最多进行n-1躺就可以把所有元素排好序。
冒泡排序的时间复杂度为O(n^2) ,是稳定的排序。
代码如下
#include<iostream>
using namespace std;
int Bsort(int *a,int n)
{
int i,j,tmp;
int count=0;
for(i=0;i<n-1;i++)//n-1躺
{
for(j=i+1;j<n;j++)//把最大的数放到最后
{
if(a[i]>a[j])//a[i]保存较小的数
{
tmp=a[i];
a[i]=a[j];
a[j]=tmp;
count++;
}
}
}
return count;
}
int main()
{
int a[10]={0,5,3,1,7,9,12,56,2,66};
int x;
x=Bsort(a,10);
for(int i=0;i<10;i++)
cout<<a[i]<<" ";
cout<<endl<<x;
return 0;
}
运行结果
快速排序
这是对冒泡排序的一种改进,基本思想基于分治法,在待排序表中任取一个元素作为基准,通过一趟排序将待排序表划分为独立的两部分,使得左面的所有元素小于基准,右面的所有元素大于基准,这个过程称为一趟快速排序。分别递归的对两个子表重复过程,直到每部分只有一个元素或者为空,所有元素放在了最终位置。
快速排序的 空间复杂度最坏情况为O(n),平均情况为(log2n)
时间复杂度 最坏为O(n^2),最理想情况为O(nlog2n)
快速排序是一种不稳定的排序。
#include<iostream>
using namespace std;
int partition(int *arr,int left,int right)
{
int temp=arr[left];
while(left<right)//直达left和right重合的时候,才找到合适的位置
{
//先从后往前找比基准小的
while(left<right && arr[right]>=temp)//当right的值大于temp的值的时候才执行
//等号一定得写,因为可能会出现,保存的temp元素和数据中的元素一样的,不写会出现死循环的现象
{
right--;
}
arr[left]=arr[right];//当right的值小于temp的值的时候执行
//从前往后找,找比基准大的
while(left<right && arr[left] <=temp)//当left的值小于temp的值的时候执行
{
left++;
}
arr[right]=arr[left];//当left的值大于temp的时候执行
}
arr[left]=temp;//此时的left和right在同一个位置,此时为合适的位置,把temp的值给left
return left;//此时返回的值是temp合适的位置,即小于它的在它的左边,大于它的在它的右边
}
void quick(int *arr,int left,int right)
{
if(left<right)
{
int pivot=partition(arr,left,right);
quick(arr,left,pivot-1);
quick(arr,pivot+1,right);
}
}
int main()
{
int arr[]={9,5,7,10,45,12};
int len=sizeof(arr)/sizeof(arr[0]);
quick(arr,0,len-1);
for(int k=0;k<len;++k)
{
cout<<arr[k]<<" ";
}
cout<<endl;
return 0;
}
三、选择排序
基本思想是 每一躺在后面n-i+1个待排元素中选取关键字最小的元素,作为有序子序列的第i个元素,直到第n-1躺完成,待排序元素剩下一个,无须再选。
简单选择排序
每次排序从L[i…n]选取关键字最小元素和L(i)交换,n-1躺使整个排序表有序。
时间复杂度始终O(n^2)
伪代码
void selectsort(ElemType A[],int n)
{
for(i=0;i<n-1;i++)
{
min=i;
for(j=i+1;j<n;j++)
{
if(A[j]<A[min]) min=j;
}
if(min!=j)
swap(A[i],A[min]);
}
}
堆排序
堆排序是一种树形选择排序方法,特点是排序过程中,将L[1…n]看做一颗完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系,在当前无序区中选择关键字最大的元素。堆常被用来实现优先级队列,在操作系统中的作业调度和其他领域有广泛应用。
在最好最坏和平均情况下,堆排序的时间复杂度为O(nlog2n)
总结