本节分析及实现8大排序算法及其改进。使用了外存(一般是磁盘)的排序称为外部排序,否则是内部排序。
1、冒泡排序
每次排序将一个最大值放到待排序数组的末尾。实现如下:
public class Sort{
private int[] array;
public Sort(int[] array){
this.array = array;
}
public void displayArray(){
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;i++){
for(int j=0;j<len-i-1;j++){
if(array[j] > array[j+1]){
temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
}
}
System.out.println("第" + (i+1)+"轮排序");
displayArray();
}
}
public static void main(String[] args){
int[] testArray = {3,6,4,2,11,10,5};
Sort sort = new Sort(testArray);
sort.bubbleSort();
}
}
改进方案:设置一个是否交换的标志exchangeFlag,如果某一次排序没有发生交换,说明已经排列好,可立即结束排序。
public class Sort{
private int[] array;
public Sort(int[] array){
this.array = array;
}
public void displayArray(){
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;i++){
boolean exchageFlag = false;
for(int j=0;j<len-i-1;j++){
if(array[j] > array[j+1]){
temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
if(!exchageFlag){
exchageFlag = true;
}
}
}
System.out.println("第" + (i+1)+"轮排序");
displayArray();
if(!exchageFlag){
break;
}
}
}
public static void main(String[] args){
int[] testArray = {3,6,4,2,11,10,5};
Sort sort = new Sort(testArray);
sort.bubbleSort();
}
}
进一步改进:某一轮排序中,后面一段数据没有发生数据交换,则下次不再比较后面一段数据。
public class Sort{
private int[] array;
public Sort(int[] array){
this.array = array;
}
public void displayArray(){
for(int i = 0;i<array.length;i++){
System.out.print(array[i] + "\t");
}
System.out.println();
}
public void bubbleSort(){
int temp;
int counter = 1;
int endPoint = array.length-1;
while(endPoint > 0){
int pos = 0;
for(int j=0;j<endPoint;j++){
if(array[j] > array[j+1]){
temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
pos = j+1;
}
}
counter++;
endPoint = pos - 1;
System.out.println("第" + counter+"轮排序");
displayArray();
}
}
public static void main(String[] args){
int[] testArray = {3,6,4,2,11,10,5};
Sort sort = new Sort(testArray);
sort.bubbleSort();
}
}
再进一步改进,正向冒泡确定最大值,反向冒泡确定最小值:
public class Sort{
private int[] array;
public Sort(int[] array){
this.array = array;
}
public void displayArray(){
for(int i = 0;i<array.length;i++){
System.out.print(array[i] + "\t");
}
System.out.println();
}
public void bubbleSort(){
int temp = 0;
int low =0,high = array.length-1;
int counter = 0;
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;
counter++;
System.out.println("第" +counter +"轮排序");
displayArray();
}
}
public static void main(String[] args){
int[] testArray = {3,6,4,2,11,10,5};
Sort sort = new Sort(testArray);
sort.bubbleSort();
}
}
2、选择排序
从第一个元素开始,扫描整个待排数组,找到最小的元素放之后再与第一个元素交换位置,然后再从第二个元素开始,继续寻找最小的元素与第二个元素交换位置,依次类推。
public class Sort{
private int[] array;
public Sort(int[] array){
this.array = array;
}
public void displayArray(){
for(int i = 0;i<array.length;i++){
System.out.print(array[i] + "\t");
}
System.out.println();
}
public void bubbleSort(){
int temp = 0;
int low =0,high = array.length-1;
int counter = 0;
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;
counter++;
System.out.println("第" +counter +"轮排序");
displayArray();
}
}
public void selectSort(){
int minPoint;
int len = array.length;
int temp;
int counter = 0;
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 = j;
}
}
if(minPoint != i){
temp = array[i];
array[i] = array[minPoint];
array[minPoint] = temp;
}
counter++;
System.out.println("第" +counter +"轮排序");
displayArray();
}
}
public static void main(String[] args){
int[] testArray = {3,6,4,2,11,10,5};
Sort sort = new Sort(testArray);
//sort.bubbleSort();
sort.selectSort();
}
}
选择排序改进:每次排序确定最大值和最小值,将排序趟数减少一半
public void selectSort(){
int minPoint;
int maxPoint;
int len = array.length;
int temp;
int counter = 0;
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 = j;
}
if(array[j] > array[maxPoint]){
maxPoint = j;
}
}
if(minPoint != i){
temp = array[i];
array[i] = array[minPoint];
array[minPoint] = temp;
}
if(maxPoint == i){
maxPoint= minPoint;
}
if(maxPoint != len-1-i){
temp = array[len-1-i];
array[len-1-i] = array[maxPoint];
array[maxPoint] = temp;
}
counter++;
System.out.println("第" +counter +"轮排序");
displayArray();
}
}
3、插入排序
在要排序的一组数中,假设前面(n-1)[n>=2] 个数已经是排好顺序的,现在要把第n个数找到相应位置并插入,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。相对于随机数据,这个算法比冒泡排序快一倍,比选择排序略快。
public void insertSort(){
int len = array.length;
int counter = 0;
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;
counter++;
System.out.println("第" +counter +"轮排序");
displayArray();
}
}
插入排序改进:二分插入排序
public int binarySearch(int low,int high,int target){
int curIndex;
while(low < high){
curIndex = (low + high)/2;
if(array[curIndex] > target){
high = curIndex - 1;
}else{
low = curIndex + 1;
}
}
return low;
}
public void binaryInsertSort(){
int len = array.length;
int counter = 0;
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;
}
counter++;
System.out.println("第" +counter +"轮排序");
displayArray();
}
}
另一种改进:2-路插入排序,就不细讲,参考:https://blog.csdn.net/u012152619/article/details/47306209
4、归并排序
归并排序先将一个无序的N长数组切成N个有序子序列(只有一个数据的序列认为是有序序列),然后两两合并,再将合并后的N/2(或者N/2 + 1)个子序列继续进行两两合并,以此类推得到一个完整的有序数组。
归并排序的核心思想是将两个有序的数组归并到另一个数组中,所以需要开辟额外的空间。
private void merge(int[] workSpace,int low,int mid,int high){
int lowBegin = low;
int lowEnd = mid;
int highBegin = mid + 1;
int highEnd = high;
int j = 0;
int n = high - low + 1;
while(lowBegin<=lowEnd && highBegin<=highEnd){
if(array[lowBegin]<array[highBegin]){
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[low++] = workSpace[j];
}
}
private void recursiveMergeSort(int[] workSpace,int low,int high){
if(low == high){
return;
}else{
int mid = (low + high) / 2;
recursiveMergeSort(workSpace,low,mid);
recursiveMergeSort(workSpace,mid+1,high);
merge(workSpace,low,mid,high);
displayArray();
}
}
public void mergeSort(){
int[] workSpace = new int[array.length];
recursiveMergeSort(workSpace,0,workSpace.length-1);
}
一般来讲,复制操作的时间消耗要远大于比较操作的时间消耗,时间复杂度是由复制次数主导的。
5、快速排序
快速排序也是基于分治算法得。步骤如下:
(1)选择一个基准元素,通常选择第一个元素或者最后一个元素;
(2)通过一趟排序讲待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小。另一部分记录的元素值比基准值大;
(3)此时基准元素在其排好序后的正确位置;
(4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。
public void quickSort(){
recursiveQuickSort(0,array.length-1);
}
private void recursiveQuickSort(int low,int high){
if(low >= high){
return;
}else{
int pivot = array[low];
int partition = partition(low,high,pivot);
displayArray();
recursiveQuickSort(low,partition-1);
recursiveQuickSort(partition+1,high);
}
}
private int partition(int low,int high,int pivot){
while(low < high){
while(low < high && array[high]>=pivot){
high--;
}
swap(low,high);
while(low < high && array[low] <= pivot){
low++;
}
swap(low,high);
}
return low;
}
private void swap(int low,int high){
int temp = array[low];
array[low] = array[high];
array[high] = temp;
}
6、希尔排序
希尔排序是基于插入排序的,又叫缩小增量排序。
希尔排序通过加大插入排序时元素之间的间隔,并对这些间隔的元素进行插入排序,从而使数据能大跨度地移动。数据项之间的间隔被称为增量,习惯上还用h表示。h=3*h+1
public void shellSort(){
int len = array.length;
int counter = 0;
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-1)/3;
counter++;
System.out.println("第" +counter +"轮排序");
displayArray();
}
}
public void shellInsertSort(int beginIndex,int increment){
int targetIndex = beginIndex + increment;
while(targetIndex < array.length){
int temp = array[targetIndex];
int previousIndex = targetIndex - increment;
while(previousIndex>=0 && array[previousIndex] > temp){
array[previousIndex+increment] = array[previousIndex];
previousIndex = previousIndex - increment;
}
array[previousIndex+increment] = temp;
targetIndex = targetIndex + increment;
}
}
7、堆排序
(1)堆中某个节点的值总是不大于或不小于其父节点的值;
(2)堆总是一棵完全二叉树(Complete Binary Tree)。
除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树称为满二叉树。如果除最后一层外,每一层上的节点数均达到最大值;在最后一层上只缺少右边的若干结点,这样的二叉树被称为完全二叉树。
棵完全二叉树,如果某个节点的值总是不小于其父节点的值,则根节点的关键字是所有节点关键字中最小的,称为小根堆(小顶堆);如果某个节点的值总是不大于其父节点的值,则根节点的关键字是所有节点关键字中最大的,称为大根堆(大顶堆)。
可以发现,如果某个节点的编号为i,则它的子节点的编号分别为:2i、2i+1。具有n个元素的序列(k1,k2,...,kn),当且仅当满足
时称之为堆。
初始时把要排序的n个数看作是一棵顺序存储的完全二叉树,调整它们的存储顺序,使之成为一个堆,将堆顶元素输出,得到n 个元素中最小(最大)的元素,这时堆的根节点的数最小(或者最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n 个元素中次小(或次大)的元素。依次类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。这个过程就称为堆排序。
public void heapSort(){
buildHeap();
System.out.println("建堆");
printTree(array.length);
int lastIndex = array.length - 1;
while(lastIndex>0){
swap(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--){
adjustHeap(i,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 maxWidgth = (int)Math.pow(2,layers)-1;
int endSpacing = maxWidgth;
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.println(array[beginIndex++]+"");
for(int k = 0;k<spacing;k++){
System.out.print(" ");
}
if(beginIndex == len){
break;
}
}
System.out.println();
}
System.out.println();
}
8、基数排序(桶排序)用的比较少,就不细说了。
八大排序算法比较:
实际使用中,经过实验,会发现相同的数据规模,快速排序比堆排序的效率高很多,并且随着数据规模的扩大,二者的差距不断扩大,快速排序的优势越来越明显。快速排序的时间复杂度近似线性增长,堆排序则要大很多。