稳定的排序算法有:直接插入,冒泡,归并,基数排序。
一.快速排序
快排的三个步骤:
1.选取枢纽元,一般用三数中值法,即求得left,center,right的中位数(不要用第一个数,如果原始数据是倒序,效率将会很低)。
2.根据枢纽元把输入数据划分成为两部分,左半部分的数比枢纽元小,右半部分比枢纽元大。
3. 分别对左半部分和右半部分递归调用快排,最终得到排序结果。
最难的是第二步,也就是分区函数。
首现把枢纽元放在最左边,设置两个指针low和high,分别指向除枢纽元外的左右两端;
找到左边第一个比枢纽元大的数,并且找到右边第一个枢纽元小的数,如果low<high,那么就交换两者,继续循环,否则跳出循环;
在循环条件部分,左边元素的判断,一定要加等号和low<high,即:
array[low] <= pivot && low < high
右半部分则不用加
上次快排代码写错了,纠正一下,中间while(true)的部分是改完后的代码,交换分割特别要注意
快排代码
public void quickSort(int[] array,int left,int right){
if(left < right){
int p = partition(array,left,right);
//对左右两边的两部分再递归调用快排
quickSort(array,left,p-1);
quickSort(array,p+1,right);
}
}
/*
* 将其他数与枢纽元比较,比枢纽元小的放在枢纽元左边,比它大的放在右边,划分为两个部分
*/
public int partition(int[] array,int left,int right){
//选取枢纽元,不要选择第一个元素,一般用三数中值法求枢纽元,把枢纽元放到最左边
int pivot = median3(array,left,right);
int low = left+1;//从第二个数开始找
int high = right;//找到第一个比枢纽元小的数,两者交换
while(true){
//找到第一个比枢纽元大的数
while(array[low] <= pivot && low < high)low++;
//找到第一个第枢纽元小的数
while(array[high] > pivot)high--;
//如果左指针<右指针,则将两者交换
if(low < high){
swap(array,low,high);
}else{
break;
}
}
/*
* 将放在最左边的枢纽元与high指针上的数交换,
* 至此枢纽元左边的数都是小于它的数,右边的数都是大于它的数
*/
swap(array,left,high);
return high;
}
/*
* 三数中值分割法确定枢纽元,取left,right,center三个数的中值
*/
public int median3(int[] array ,int left,int right){
int center = (left+right)/2;
if(array[left] > array[center]){
swap(array,left,center);
}
if(array[left] > array[right]){
swap(array,left,right);
}
if(array[right] < array[center]){
swap(array,right,center);
}
//经过以上三个判断,中值在center位上
swap(array,center,left);//把枢纽元放在最左边
return array[left];//枢纽元的值
}
/*
* 将i和j上的数字交换
*/
public void swap(int[] array,int i,int j){
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
主程序调用:
int [] array = {0,2,5,3,2,1,4,2,7,10,2,11,3,4,6,1,3,45,10};
quickSort.quickSort(array, 0, array.length-1);
用同样的测试数据,可以做实验得到冒泡排序,需要交换的次数是50次,而快排只需要32次,故快排比冒泡快一些
在数据量小于20时,用插入排序速度比快排要快,而数据量大于20后,就尽量用快排
二.归并排序
思路:递归+合并有序数组。
首先将数组分成左右两部分,然后分别递归归并左右部分。然后将已排好序的两部分合并。
合并的算法需要用到临时变量,所以归并排序空间复杂度为O(N).
void mergeSort(int[] a){
int length = a.length;
int[] temp = new int[length];//需要一个临时变量作为合并两个有序数组用
mergeSortRec(a,temp,0,length-1);
}
void mergeSortRec(int[] a,int[] temp,int left,int right){
if(left<right){
int center = (left+right)/2;
mergeSortRec(a,temp,left,center);//归并排序左半部分
mergeSortRec(a,temp,center+1,right);//归并排序右半部分
mergerTwoSortedArrays(a,temp,left,center,right);
}
}
/*
* 将a[leftPos,leftEnd]和a[leftEnd+1,rightEnd]合并成一个有序数组
* 这两部分已有序
*/
void mergerTwoSortedArrays(int[] a,int[] temp,int leftPos,int leftEnd,int rightEnd){
int i=leftPos;//左半部分指针
int j=leftEnd+1;//右半部分指针
int k = leftPos;//临时数组的指针
while(i<=leftEnd && j<=rightEnd){
if(a[i]<a[j]){
temp[k]=a[i];
i++;
}else{
temp[k]=a[j];
j++;
}
k++;
}
while(i<=leftEnd){//右半部分全部遍历完,遍历左半部分
temp[k]=a[i];
k++;i++;
}
while(j<=rightEnd){//左半部分全部遍历完,遍历右半部分
temp[k]=a[j];
k++;j++;
}
/*
* 把temp里的数copy回原来的数组
*/
for(i=leftPos;i<=rightEnd;i++){
a[i]=temp[i];
}
}
三.堆排序
对于堆的定义,在最大堆/最小堆中有详细分析。
堆排序一般利用最大堆进行排序,主要包括两个步骤:
1. 初始化堆,建立最大堆。从最后一个叶子节点的父节点开始到下标0依次调整每个节点。
2. 删除最大的数,方法是将最大的数与最后的叶子节点交换,然后在a[0-i]范围内,对下标0的节点继续调整成为大根堆。
3. 每次调整成为一个最大堆后,重复步骤2。
void heapSort(int[] a){
int length = a.length;
//初始化堆,建立最大堆
int middle = length/2-1;//最后一个叶子节点的父节点
for(int i=middle;i>=0;i--){
heapAdjust(a,i,length);
}
int temp=0;
//删除堆的根节点(把它与最后的叶子节点(除了已排好序的)交换)
for(int i=length-1;i>=0;i--){
temp = a[0];
a[0] = a[i];
a[i] = temp;
//因为根节点被交换到最后,所以要从根节点重新调整堆
heapAdjust(a,0,i);
}
}
/*
* 在a[0..n-1]范围内调整第i个数,进行下沉操作,调整成最大堆
*/
void heapAdjust(int[] a,int i,int n){
int leftChild = 2*i;
int rightChild = leftChild+1;
int largerIndex = i;//用于比较根节点与左右孩子的值
int temp=0;
while(leftChild < n || rightChild < n){
if(leftChild < n && a[leftChild]>a[largerIndex]){//如果左孩子比根大
largerIndex = leftChild;
}
if(rightChild < n && a[rightChild]>a[largerIndex]){
largerIndex = rightChild;
}
if(largerIndex != i){//如果根节点需要调整,即左右孩子有比它大的
//交换a[largerIndex]与a[i]
temp = a[largerIndex];
a[largerIndex] = a[i];
a[i] = temp;
//继续调整被交换的那个孩子节点
i = largerIndex;
leftChild = 2*i;
rightChild = leftChild+1;
}else{//无需调整
break;
}
}
}
一些结论:
1. 堆排序和快速排序的复杂度都是nlogn,但一般使用快速排序。因为堆排序在二叉树用上浮的方式构造堆,元素交换的跨度较大。