一、八大排序算法总结
八大排序算法的稳定性及复杂度总结如下:
二、选择排序算法准则
每种排序算法都各有优缺点。因此,在实用时需根据不同情况适当选用,甚至可以将多种方法结合起来使用。
影响排序的因素有很多,平均时间复杂度低的算法并不一定就是最优的。相反,有时平均时间复杂度高的算法可能更适合某些特殊情况。同时,选择算法时还得考虑它的可读性,以利于软件的维护。一般而言,需要考虑的因素有以下四点:
1.待排序的记录数目n的大小;
2.记录本身数据量的大小,也就是记录中除关键字外的其他信息量的大小;
3.关键字的结构及其分布情况;
4.对排序稳定性的要求。
设待排序元素的个数为n。
1)当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序。
快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
堆排序:如果内存空间允许且要求稳定性的,
归并排序:它有一定数量的数据移动,所以我们可能过与插入排序组合,先获得一定长度的序列,然后再合并,在效率上将有所提高。
2)当n较大,内存空间允许,且要求稳定性,推荐归并排序
3)当n较小,可采用直接插入或直接选择排序。
直接插入排序:当元素分布有序,直接插入排序将大大减少比较次数和移动记录的次数。
直接选择排序 :元素分布有序,如果不要求稳定性,选择直接选择排序
4)一般不使用或不直接使用传统的冒泡排序。
5)基数排序
它是一种稳定的排序算法,但有一定的局限性:
1、关键字可分解。
2、记录的关键字位数较少,如果密集更好
3、如果是数字时,最好是无符号的,否则将增加相应的映射复杂度,可先将其正负分开排序。
三、java实现
1、简单排序:冒泡排序 、选择排序 、插入排序
package com.xin.sort;
public class Sort {
private int [] array;
public Sort(int [] array){
this.array = array;
}
//按顺序打印数组中的元素
public void display(){
for(int i=0;i<array.length;i++){
System.out.print(array[i]+"\t");
}
System.out.println();
}
//冒泡排序
public void bubbleSort(){
int temp;
int len = array.length;
for(int i=0;i<len-1;i++) { //外层循环:每循环一次就确定了一个相对最大元素
for(int j=1;j<len-i;j++) { //内层循环:有i个元素已经排好,根据i确定本次的比较次数
if(array[j-1] >array[j]) { //如果前一位大于后一位,交换位置
temp = array[j-1];
array[j-1] = array[j];
array[j] = temp;
}
}
System.out.print("第"+(i+1)+"轮排序结果:");
display();
}
}
//冒泡排序改进1
public void bubbleSort_improvement_1(){
int temp;
int len = array.length;
for(int i=0;i<len-1;i++){
boolean exchange = false; //设置交换变量
for(int j=1;j<len-i;j++){
if(array[j-1]>array[j]){ //如果前一位大于后一位,交换位置
temp = array[j-1];
array[j-1] = array[j];
array[j] = temp;
if(!exchange) exchange =true; //发生了交换操作
}
}
System.out.print("第"+(i+1)+"轮排序结果:");
display();
if(!exchange) break; //如果上一轮没有发生交换数据,证明已经是有序的了,结束排序
}
}
//冒泡排序改进2
public void bubbleSort_improvement_2(){
int temp;
int counter = 1;
int endPoint = array.length-1; //endPoint代表最后一个需要比较的元素下标
while(endPoint>0){
int pos = 1;
for(int j=1;j<=endPoint;j++){
if(array[j-1]>array[j]){ //如果前一位大于后一位,交换位置
temp= array[j-1];
array[j-1]= array[j];
array[j]= temp;
pos= j; //下标为j的元素与下标为j-1的元素发生了数据交换
}
}
endPoint= pos-1; //下一轮排序时只对下标小于pos的元素排序,下标大于等于pos的元素已经排好
System.out.print("第"+counter+"轮排序结果:");
counter++;
display();
}
}
//冒泡排序改进3
public void bubbleSort_improvement_3(){
int temp;
int low = 0;
int high = array.length-1;
int counter = 1;
while(low<high){
for(int i=low;i<high;++i){ //正向冒泡,确定最大值
if(array[i]>array[i+1]){ //如果前一位大于后一位,交换位置
temp= array[i];
array[i]= array[i+1];
array[i+1]= temp;
}
}
--high;
for(int j=high;j>low;--j){ //反向冒泡,确定最小值
if(array[j]<array[j-1]){ //如果前一位大于后一位,交换位置
temp= array[j];
array[j]= array[j-1];
array[j-1]= temp;
}
}
++low;
System.out.print("第"+counter+"轮排序结果:");
display();
counter++;
}
}
//选择排序
public void selectionSort(){
int minPoint; //存储最小元素的小标
int len = array.length;
int temp;
int counter = 1;
for(int i=0;i<len-1;i++){
minPoint= i;
for(int j=i+1;j<=len-1;j++){ //每完成一轮排序,就确定了一个相对最小元素,下一轮排序只对后面的元素排序
if(array[j]<array[minPoint]){ //如果待排数组中的某个元素比当前元素小,minPoint指向该元素的下标
minPoint= j;
}
}
if(minPoint!=i){ //如果发现了更小的元素,交换位置
temp= array[i];
array[i]= array[minPoint];
array[minPoint]= temp;
}
System.out.print("第"+counter+"轮排序结果:");
display();
counter++;
}
}
//选择排序改进版
public void selectionSort_improvement(){
int minPoint; //存储最小元素的小标
int maxPoint; //存储最大元素的小标
int len = array.length;
int temp;
int counter = 1;
for(int i=0;i<len/2;i++){
minPoint= i;
maxPoint= i;
for(int j=i+1;j<=len-1-i;j++){ //每完成一轮排序,就确定了两个最值,下一轮排序时比较范围减少两个元素
if(array[j]<array[minPoint]){ //如果待排数组中的某个元素比当前元素小,minPoint指向该元素的下标
minPoint= j;
continue;
}else if(array[j]>array[maxPoint]){ //如果待排数组中的某个元素比当前元素大,maxPoint指向该元素的下标
maxPoint= j;
}
}
if(minPoint!=i){ //如果发现了更小的元素,与第一个元素交换位置
temp= array[i];
array[i]= array[minPoint];
array[minPoint]= temp;
//原来的第一个元素已经与下标为minPoint的元素交换了位置
//如果之前maxPoint指向的是第一个元素,那么需要将maxPoint重新指向array[minPoint]
//因为现在array[minPoint]存放的才是之前第一个元素中的数据
if(maxPoint== i){
maxPoint= minPoint;
}
}
if(maxPoint!=len-1-i){ //如果发现了更大的元素,与最后一个元素交换位置
temp= array[len-1-i];
array[len-1-i]= array[maxPoint];
array[maxPoint]= temp;
}
System.out.print("第"+counter+"轮排序结果:");
display();
counter++;
}
}
//插入排序
public void insertionSort(){
int len = array.length;
int counter = 1;
for(int i=1;i<len;i++){
int temp = array[i]; //存储待排序的元素值
int insertPoint = i-1; //与待排序元素值作比较的元素的下标
while(insertPoint>=0 && array[insertPoint]>temp){ //当前元素比待排序元素大
array[insertPoint+1]= array[insertPoint]; //当前元素后移一位
insertPoint--;
}
array[insertPoint+1]= temp; //找到了插入位置,插入待排序元素
System.out.print("第"+counter+"轮排序结果:");
display();
counter++;
}
}
//二分插入排序
public void BinaryInsertionSort(){
int len = array.length;
int counter = 1;
for(int i=1;i<len;i++){
int temp = array[i]; //存储待排序的元素值
if(array[i-1]>temp){ //比有序数组的最后一个元素要小
int insertIndex = binarySearch(0, i-1, temp); //获取应插入位置的下标
for(int j=i;j>insertIndex;j--){ //将有序数组中,插入点之后的元素后移一位
array[j]= array[j-1];
}
array[insertIndex]= temp; //插入待排序元素到正确的位置
}
System.out.print("第"+counter+"轮排序结果:");
display();
counter++;
}
}
/**
* 二分查找法
* @param lowerBound 查找段的最小下标
* @param upperBound 查找段的最大下标
* @param target 目标元素
* @return 目标元素应该插入位置的下标
*/
public int binarySearch(int lowerBound,int upperBound,int target){
int curIndex;
while(lowerBound<upperBound){
curIndex= (lowerBound+upperBound)/2;
if(array[curIndex]>target){
upperBound= curIndex - 1;
}else{
lowerBound= curIndex + 1;
}
}
return lowerBound;
}
//2-路插入排序
public void two_wayInsertionSort(){
int len = array.length;
int [] newArray = new int [len];
newArray[0]= array[0]; //将原数组的第一个元素作为枢纽元素
int first = 0; //指向最小元素的指针
int last = 0; //指向最大元素的指针
for(int j=0;j<newArray.length;j++){ //打印初始化数组
System.out.print(newArray[j]+"\t");
}
System.out.println();
for(int i=1;i<len;i++){
if(array[i]>= newArray[last]){ //大于等于最大元素,直接插入到last后面,不用移动元素
last++;
newArray[last]= array[i];
}else if(array[i] < newArray[first]){ //小于最小元素,直接插到first前面,不用移动元素
first= (first-1+len) % len;
newArray[first]= array[i];
}else if(array[i] >= newArray[0]){ //在最大值与最小值之间,且大于等于枢纽元素,插入到last之前,需要移动元素
int curIndex = last;
last++;
do{ //比array[i]大的元素后移一位
newArray[curIndex+1]= newArray[curIndex];
curIndex--;
}while(newArray[curIndex]>array[i]);
newArray[curIndex+1]= array[i]; //插入到正确的位置
}else{ //在最大值与最小值之间,且小于枢纽元素,插入到first之后,需要移动元素
int curIndex = first;
first= (first-1+len) % len;
do{ //比array[i]小的元素前移一位
newArray[curIndex-1]= newArray[curIndex];
curIndex= (curIndex+1+len)%len;
}while(newArray[curIndex]<=array[i]);
newArray[(curIndex-1+len)%len]= array[i]; //插入到正确的位置
}
for(int j=0;j<newArray.length;j++){ //打印新数组中的元素
System.out.print(newArray[j]+"\t");
}
System.out.println();
}
}
}
2、 高级排序:归并排序 、快速排序 、希尔排序 、堆排序 、基数排序
package com.xin.sort;
import java.util.ArrayList;
import java.util.List;
/**
* 高级排序
* @author Administrator
*
*/
public class HighSort {
private int [] array; //待排序的数组
public HighSort(int [] array){
this.array= array;
}
//按顺序打印数组中的元素
public void display(){
for(int i=0;i<array.length;i++){
System.out.print(array[i]+"\t");
}
System.out.println();
}
//归并排序
public void mergeSort(){
int[] workSpace = new int [array.length]; //用于辅助排序的数组
recursiveMergeSort(workSpace,0,workSpace.length-1);
}
/**
* 递归的归并排序
* @param workSpace 辅助排序的数组
* @param lowerBound 欲归并数组段的最小下标
* @param upperBound 欲归并数组段的最大下标
*/
private void recursiveMergeSort(int [] workSpace,int lowerBound,int upperBound){
if(lowerBound== upperBound){ //该段只有一个元素,不用排序
return;
}else{
int mid = (lowerBound+upperBound)/2;
recursiveMergeSort(workSpace,lowerBound,mid); //对低位段归并排序
recursiveMergeSort(workSpace,mid+1,upperBound); //对高位段归并排序
merge(workSpace,lowerBound,mid,upperBound);
display();
}
}
/**
* 对数组array中的两段进行合并,lowerBound~mid为低位段,mid+1~upperBound为高位段
* @param workSpace 辅助归并的数组,容纳归并后的元素
* @param lowerBound 合并段的起始下标
* @param mid 合并段的中点下标
* @param upperBound 合并段的结束下标
*/
private void merge(int [] workSpace,int lowerBound,int mid,int upperBound){
int lowBegin = lowerBound; //低位段的起始下标
int lowEnd = mid; //低位段的结束下标
int highBegin = mid+1; //高位段的起始下标
int highEnd = upperBound; //高位段的结束下标
int j = 0; //workSpace的下标指针
int n = upperBound-lowerBound+1; //归并的元素总数
while(lowBegin<=lowEnd && highBegin<=highEnd){
if(array[lowBegin]<array[highBegin]){//将两者较小的那个放到workSpace中
workSpace[j++]= array[lowBegin++];
}else{
workSpace[j++]= array[highBegin++];
}
}
while(lowBegin<=lowEnd){
workSpace[j++]= array[lowBegin++];
}
while(highBegin<=highEnd){
workSpace[j++]= array[highBegin++];
}
for(j=0;j<n;j++){ //将归并好的元素复制到array中
array[lowerBound++]= workSpace[j];
}
}
//快速排序
public void quikSort(){
recursiveQuikSort(0,array.length-1);
}
/**
* 递归的快速排序
*@param low 数组的最小下标
*@param high 数组的最大下标
*/
private void recursiveQuikSort(int low,int high){
if(low>=high){
return;
}else{
int pivot = array[low]; //以第一个元素为基准
int partition =partition(low,high,pivot); //对数组进行划分,比pivot小的元素在低位段,比pivot大的元素在高位段
display();
recursiveQuikSort(low,partition-1); //对划分后的低位段进行快速排序
recursiveQuikSort(partition+1,high); //对划分后的高位段进行快速排序
}
}
/**
* 以pivot为基准对下标low到high的数组进行划分
*@param low 数组段的最小下标
*@param high 数组段的最大下标
*@param pivot 划分的基准元素
*@return 划分完成后基准元素所在位置的下标
*/
private int partition(int low,int high,int pivot){
while(low<high){
while(low<high &&array[high]>=pivot){ //从右端开始扫描,定位到第一个比pivot小的元素
high--;
}
swap(low,high);
while(low<high &&array[low]<=pivot){ //从左端开始扫描,定位到第一个比pivot大的元素
low++;
}
swap(low,high);
}
return low;
}
/**
* 交换数组中两个元素的数据
*@param low 欲交换元素的低位下标
*@param high 欲交换元素的高位下标
*/
private void swap(int low,int high){
int temp = array[high];
array[high] = array[low];
array[low] = temp;
}
//希尔排序
public void shellSort(){
int len = array.length;
int counter = 1;
int h = 1;
while(3*h+1<len){ //确定第一轮排序时的间隔
h = 3*h+1;
}
while(h>0){
for(int i=0;i<h;i++){
shellInsertSort(i,h); //对间隔为h的元素进行插入排序
}
h = (h-1)/3; //下一轮排序的间隔
System.out.print("第"+counter+"轮排序结果:");
display();
counter++;
}
}
/**
* 希尔排序内部使用的插入排序:
* 需要进行插入排序的元素为array[beginIndex]、array[beginIndex+increment]、array[beginIndex+2*increment]...
*@param beginIndex 起始下标
*@param increment 增量
*/
private void shellInsertSort(int beginIndex,int increment){
int targetIndex =beginIndex+increment; //欲插入元素的下标
while(targetIndex<array.length){
int temp =array[targetIndex];
int previousIndex =targetIndex - increment; //前一个元素下标,间隔为increment
while(previousIndex>=0 && array[previousIndex]>temp){
array[previousIndex+increment]= array[previousIndex]; //比欲插入数据项大的元素后移一位
previousIndex =previousIndex - increment;
}
array[previousIndex+increment]= temp; //插入到合适的位置
targetIndex =targetIndex+increment; //插入下一个元素
}
}
//堆排序
public void heapSort(){
buildHeap();
System.out.println("建堆:");
printTree(array.length);
int lastIndex = array.length-1;
while(lastIndex>0){
swap(0,lastIndex); //取出堆顶元素,将堆底放入堆顶。其实就是交换下标为0与lastIndex的数据
if(--lastIndex == 0) break; //只有一个元素时就不用调整堆了,排序结束
adjustHeap(0,lastIndex); //调整堆
System.out.println("调整堆:");
printTree(lastIndex+1);
}
}
/**
* 用数组中的元素建堆
*/
private void buildHeap(){
int lastIndex = array.length-1;
for(int i= (lastIndex-1)/2;i>=0;i--){ //(lastIndex-1)/2就是最后一个元素的根节点的下标,依次调整每棵子树
adjustHeap(i,lastIndex); //调整以下标i的元素为根的子树
}
}
/**
* 调整以下标是rootIndex的元素为根的子树
*@param rootIndex 根的下标
*@param lastIndex 堆中最后一个元素的下标
*/
private void adjustHeap(int rootIndex,int lastIndex){
int biggerIndex = rootIndex;
int leftChildIndex = 2*rootIndex+1;
int rightChildIndex = 2*rootIndex+2;
if(rightChildIndex<=lastIndex){ //存在右子节点,则必存在左子节点
if(array[rootIndex]<array[leftChildIndex] || array[rootIndex]<array[rightChildIndex]){ //子节点中存在比根更大的元素
biggerIndex = array[leftChildIndex]<array[rightChildIndex] ? rightChildIndex :leftChildIndex;
}
}else if(leftChildIndex<=lastIndex){ //只存在左子节点
if(array[leftChildIndex]>array[rootIndex]){ //左子节点更大
biggerIndex = leftChildIndex;
}
}
if(biggerIndex != rootIndex){ //找到了比根更大的子节点
swap(rootIndex,biggerIndex);
//交换位置后可能会破坏子树,将焦点转向交换了位置的子节点,调整以它为根的子树
adjustHeap(biggerIndex,lastIndex);
}
}
/**
* 将数组按照完全二叉树的形式打印出来
*/
private void printTree(int len){
int layers = (int)Math.floor(Math.log((double)len)/Math.log((double)2))+1; //树的层数
int maxWidth = (int)Math.pow(2,layers)-1; //树的最大宽度
int endSpacing = maxWidth;
int spacing;
int numberOfThisLayer;
for(int i=1;i<=layers;i++){ //从第一层开始,逐层打印
endSpacing = endSpacing/2; //每层打印之前需要打印的空格数
spacing = 2*endSpacing+1; //元素之间应该打印的空格数
numberOfThisLayer = (int)Math.pow(2, i-1); //该层要打印的元素总数
int j;
for(j=0;j<endSpacing;j++){
System.out.print(" ");
}
int beginIndex = (int)Math.pow(2,i-1)-1; //该层第一个元素对应的数组下标
for(j=1;j<=numberOfThisLayer;j++){
System.out.print(array[beginIndex++]+"");
for(int k=0;k<spacing;k++){ //打印元素之间的空格
System.out.print(" ");
}
if(beginIndex == len){ //已打印到最后一个元素
break;
}
}
System.out.println();
}
System.out.println();
}
//基数排序
public void radixSort(){
int max = array[0];
for(int i=0;i<array.length;i++){ //找到数组中的最大值
if(array[i]>max){
max = array[i];
}
}
int keysNum = 0; //关键字的个数,我们使用个位、十位、百位...当做关键字,所以关键字的个数就是最大值的位数
while(max>0){
max /= 10;
keysNum++;
}
List<ArrayList<Integer>> buckets = new ArrayList<ArrayList<Integer>>();
for(int i=0;i<10;i++){ //每位可能的数字为0~9,所以设置10个桶
buckets.add(new ArrayList<Integer>()); //桶由ArrayList<Integer>构成
}
for(int i=0;i<keysNum;i++){ //由最次关键字开始,依次按照关键字进行分配
for(int j=0;j<array.length;j++){ //扫描所有数组元素,将元素分配到对应的桶中
//取出该元素对应第i+1位上的数字,比如258,现在要取出十位上的数字,258%100=58,58/10=5
int key =array[j]%(int)Math.pow(10, i+1)/(int)Math.pow(10, i);
buckets.get(key).add(array[j]); //将该元素放入关键字为key的桶中
}
//分配完之后,将桶中的元素依次复制回数组
int counter = 0; //元素计数器
for(int j=0;j<10;j++){
ArrayList<Integer> bucket =buckets.get(j); //关键字为j的桶
while(bucket.size()>0){
array[counter++] = bucket.remove(0); //将桶中的第一个元素复制到数组,并移除
}
}
System.out.print("第"+(i+1)+"轮排序:");
display();
}
}
}