选择排序
优点:容易实现,原地排序不需要额外的存储空间
缺点:扩展性差
void SelectSort(){
int [] array={1,5,3,2,6,7,9,13,54,20};
int min=0;//保存最元素值的下标
for(int i=0;i<array.length-1;i++){
min=i;
//查找最小数在数组中的下标
for(int j=i+1;j<array.length;j++){
if(array[min]>array[j]){
min=j;//保存最小数的下标
}
}
//如果第i个最小的数位置不在i上,则进行交换
if(i!=min){
int temp=array[i];
array[i]=array[min];
array[min]=temp;
}
}
}
冒泡排序
按照改进的算法,对于一个已经有序的数组,算法完成第一次外层循环后就会返回。
实际上只发生了 N - 1次比较,所以最好的情况下,该算法复杂度是O(n)。
void BubbleSort(){
int [] array={1,5,3,2,6,7,9,13,54,20};
for(int i=0;i<array.length-1;i++){
//每一轮比较的次数为N-1-i;
for(int j=0;j<array.length-1-i;j++){
//比较相邻的两个数,小靠前
if(array[j]>array[j+1]){
//两个数做交换.通过设置临时变量
int temp=array[j];
array[j]=array[j+1];
array[j+1]=temp;
}
}
}
}
void BubbleSortImproved(){
int [] array={1,5,3,2,6,7,9,13,54,20};
boolean swapped=true;
for(int i=0;i<array.length-1&&swapped;i++){
swapped=false;
//每一轮比较的次数为N-1-i;
for(int j=0;j<array.length-1-i;j++){
//比较相邻的两个数,小靠前
if(array[j]>array[j+1]){
//两个数做交换.通过设置临时变量
int temp=array[j];
array[j]=array[j+1];
array[j+1]=temp;
swapped=true;
}
}
}
}
插入排序
优点:实现简单,数据量少时效率高
如果输入序列已经预排序,时间复杂度为O(n+d),d是反转的次数。
算法运行效率优于选择排序冒泡排序即使是最坏的情况三个算法时间复杂度均为O($n^2$)
能在接收序列的同时进行排序
void InsertSort(){
int [] array={20,25,15,42,36,16,12};
for(int i=1;i<array.length;i++){
int temp=array[i];
//把下标保存起来
int j=i;
while(j>0&&temp<array[j-1]){
//上面的数覆盖其下面的数
array[j]=array[j-1];
j--;
}
array[j]=temp;//插入数据
}
}
希尔排序
希尔排序是基于直接插入排序的,直接插入排序在元素较少和元素基本有序时效率是不错的,但随着元素增多和有序性破坏,效率会下降的很明显。希尔排序通过分组实现跳跃式移动,保证待排序序列基本有序后再排序,就能提高效率。
void ShellSort(){
int[]a={49,38,65,97,76,13,27,49,78,34,12,64,1};
int d=a.length;
while(true){
d=d/2;
for(int x=0;x<d;x++) {
for(int i=x+d;i<a.length;i=i+d) {
int temp=a[i];
int j;
for(j=i-d;j>=0&&a[j]>temp;j=j-d) {
a[j+d]=a[j];
}
a[j+d]=temp;
}
}
if(d==1) {
break;
}
}
}
归并排序
归并排序的主要操作是归并,其主要思想是:将若干有序序列逐步归并,最终得到一个有序序列。
int[] sort(int[] o,int m,int n){
int mid;
int[] result = new int[o.length];
if(o.length == 1|| m==n){
result[0] = o[0];
}else{
mid = (m+n)/2;
int[] temp1 = new int[mid-m+1];
int[] temp2 = new int[o.length-mid+m-1];
System.arraycopy(o,0,temp1,0,mid-m+1);
System.arraycopy(o,mid-m+1,temp2,0,o.length-mid+m-1);
int[] result1 = sort(temp1,m,mid);
int[] result2 = sort(temp2,mid+1,n);
result = Merge(result1,result2);
}
return result;
}
int[] Merge(int[] i,int[] j){
int m=0,n=0,k=0;
int[] result = new int[i.length+j.length];
for(; m<i.length&&n<j.length; k++){
if(i[m]<j[n]){
result[k] = i[m++];
}else{
result[k] = j[n++];
}
}
if(m<i.length){
while(m<i.length){
result[k++] = i[m++];
}
}
if(n<j.length){
while(n<j.length){
result[k++] = j[n++];
}
}
return result;
}
快速排序
快速排序的思想是分割,是分治算法技术的一个实例。确保一个元素左边的元素都小于这个元素,右边的元素都大于这个元素,然后对这两部分分别继续进行分割,从而达到排序的效果。
void Quicksort(int[] a,int low,int high){
int temp;
if(low<high){
temp = partition(a,low,high);
Quicksort(a,low,temp-1);
Quicksort(a,temp+1,high);
}
}
int partition(int[] a,int low,int high){
int i=low;
int j=high;
while(i<j){
while(i<j&&a[i]<=r[j]) j--;//右侧扫描
if(i<j){swap(a,i,j);i++;}//小记录置前
while(i<j&&a[i]<=r[j]) i++;//左侧扫描
if(i<j){swap(a,i,j);j--}//大记录置后
}
return low;
}
void swap(int[] a,int low,int high){
if(low<high){
int temp=a[low];
a[low]=a[high];
a[high]=a[low];
}
}
堆排序
基于比较的排序,属于选择排序,优点是最坏的情况下O($n\log n$)
基本思想:首先将待排序的记录序列构造成一个堆,此时,选出了堆中所有记录的最大者,然后将它从堆中移走,并将剩余的记录再调整成堆,这样又找出了次小的记录,以此类推,直到堆中只有一个记录。
void HeapSort(int[] a,int n){
for(int i=n/2; i>=1; i--){
heapAdjust(a,i,n);//从最后一个有子节点的节点开始依次往前调整对应节点来生成大顶堆
}
for(int i=1; i<n; i++){
swap(a,1,n-i-1);//交换堆顶元素与未排序堆最后一个元素
heapAdjust(a,1,n-i);//根据调整节点重新生成大顶堆
}
}
void heapAdjust(int r[], int k, int m ){
//要筛选结点的编号为k,堆中最后一个结点的编号为m
int i=k;
int j=2*i;//到达下一层的左孩子
while (j<=m ){ //筛选还没有进行到叶子
if (j<m && r[j]<r[j+1]) j++; //左右孩子中取较大者
if (r[i]>r[j]) break;
else {
swap(r,i,j);
i=j;
j=2*i;
}
}
}
线性排序-计数排序
计数排序的基本思想是对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数(此处并非比较各元素的大小,而是通过对元素值的计数和计数值的累加来确定)。一旦有了这个信息,就可以将x直接存放到最终的输出序列的正确位置上。例如,如果输入序列中只有17个元素的值小于x的值,则x可以直接存放在输出序列的第18个位置上。
牺牲空间换取时间,当K=O(n)时计数排序速度快,否则复杂度将会更高
int[] CountSort(int[]a){
int b[] = new int[a.length];
int max = a[0],min = a[0];
for(int i:a){
if(i>max){
max=i;
}
if(i<min){
min=i;
}
}
int k=max-min+1;//这里k的大小是要排序的数组中,元素大小的极值差+1
int c[]=new int[k];
for(int i=0;i<a.length;++i){//O(n)
c[a[i]-min]+=1;//优化过的地方,减小了数组c的大小
}
for(int i=1;i<c.length;++i){//O(k)
c[i]=c[i]+c[i-1];
}
for(int i=a.length-1;i>=0;--i){//O(n)
b[--c[a[i]-min]]=a[i];//按存取的方式取出c的元素
}
return b;
}
线性排序-桶排序
与计数排序类似,桶排序也对输入加以限制来提高算法性能。换言之。如果输入的序列来自固定集合,则桶排序的效率较高。例如假设所有输入元素是在【0,k-1】上的整数集合,这就表示k是输入序列中最远距离元素的数目。桶排序采用K个计数器,第i个计数器记录第i个的元素出现次数。
static int BUCKET=10;
void BucketSort(int A[],int array_size){
int[] bucket=new int[BUCKET];
for(int i=0;i<BUCKET;i++)bucket[i]=0;
for(int i=0;i<array_size;i++)++bucket[A[i]];
for(int i=0,j=0;j<BUCKET;j++){
for(int k=bucket[j];k>0;--k){
A[i++]=j;
}
}
}
线性排序-基数排序
1)取每个元素最低有效位
2)基于最低有效位对序列中的元素进行排序,并保持具有相同位的元素的原有次序(稳定排序)
3)对下一个最低有效位重复该过程
基数排序速度取决于内部基本操作。如果输入值具有的位数长度不等。还需要对附加位进行排序,这是基数排序最慢的部分之一,也是最难进行效率优化的部分之一。
算法灵活性不如其他排序算法,对于每一种不同类型数据,基数排序都需要重写,难以编写一个处理所有数据的通用基数排序算法。
void RadixSort(int[] number, int d){ //d表示最大的数有多少位
int k = 0;
int n = 1;
int m = 1; //控制键值排序依据在哪一位
int[][]temp = new int[10][number.length]; //数组的第一维表示可能的余数0-9
int[]order = new int[10]; //数组orderp[i]用来表示该位是i的数的个数
while(m <= d) {
for(int i = 0; i < number.length; i++) {
int lsd = ((number[i] / n) % 10);
temp[lsd][order[lsd]] = number[i];
order[lsd]++;
}
for(int i = 0; i < 10; i++) {
if(order[i] != 0)
for(int j = 0; j < order[i]; j++) {
number[k] = temp[i][j];
k++;
}
order[i] = 0;
}
n *= 10;
k = 0;
m++;
}
}
性能比较
排序算法 | 平均时间复杂度 | 最好情况 | 最坏情况 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 不稳定 |
冒泡排序 | O(n^2) | O(n) | O(n^2) | O(1) | 稳定 |
插入排序 | O(n^2) | O(n) | O(n^2) | O(1) | 稳定 |
希尔排序 | O(n*log n)~O(n^2) | O(n^{1.3}) | O(n^2) | O(1) | 不稳定 |
归并排序 | O(n*log n) | O(n*log n) | O(n*log n) | O(n) | 稳定 |
快速排序 | O(n*log n) | O(n*log n) | O(n^2) | O(log n)~O(n) | 不稳定 |
堆排序 | O(n*log n) | O(n*log n) | O(n*log n) | O(1) | 不稳定 |
线性排序-计数排序 | O(n+k) | O(n+k) | O(n+k) | O(1) | 稳定 |
线性排序-桶排序 | O(n+k) | O(n+k) | O(n^2) | O(n+k) | 稳定 |
线性排序-基数排序 | O(n*k) | O(n*k) | O(n*k) | O(n+k) | 稳定 |